Repository: quarkusio/quarkus
Issue: mutable-jar build breaks docker cache

## Describe the bug
When the package type is set to `mutable-jar`, the resulting build-system.properties file changes trivially every build. This causes the "COPY lib" Dockerfile command to generate a duplicate layer. This causes very large layers (>150Mi) to be unnecessarily pushed to a docker repository.

### Expected behavior
The Dockerfile contains the comment:
> \\# We make four distinct layers so if there are application changes the library layers can be re-used

This indicates that the docker library layer is intended to be reused if the libraries don't change.

### Actual behavior
In the Quarkus gradle build system, while assembling the app for the `mutable-jar` package type, it collects all passed system properties that start with `quarkus.` and write them out to the file `build-system.properties` in the path `build/quarkus-app/lib/deployment/` using the built-in `java.util.Properties#store()` method. This causes a comment line to be added to the file with the current timestamp. There is no way to turn off this behaviour using this mechanism. This causes the `build-system.properties` file to be different each time and therefore defeating the docker layer cache.

## To Reproduce

https://github.com/cherrydev/quarkus-docker-layer-demo

Steps to reproduce the behavior:
* `$ ./mvnw package -Dquarkus.package.type=mutable-jar`
* Examine `target/quarkus-app/lib/deployment/built-system.properties` file.
* Note the timestamp on the second line of file
* re-run `$ ./mvnw package -Dquarkus.package.type=mutable-jar`
* Note a different timestamp on the file

## Environment (please complete the following information):

### Output of `uname -a` or `ver`
Darwin [redacted] 20.5.0 Darwin Kernel Version 20.5.0: Sat May  8 05:10:33 PDT 2021; root:xnu-7195.121.3~9/RELEASE_X86_64 x86_64
### Output of `java -version`
openjdk version "11.0.11" 2021-04-20 LTS

### GraalVM version (if different from Java)

### Quarkus version or git rev
2.0.0.FInal
### Build tool (ie. output of `mvnw --version` or `gradlew --version`)
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
## Additional context

This is trivial to fix, though not very clean without re-implementing `java.util.Properties#store()`. In `JarResultBuildStep` in the `quarkus-core-deployment` project, just pass `Properties#store()` an intermediary `StringWriter`, split up the resulting string by lines and output all the lines other than the second one into the file.

In my current project, this is saving 150Mi of duplicated docker layers each time I push them to my repository.

Changed files:
core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java
core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java b/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java
index 28b533153b4..297a94b3752 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java
@@ -3,6 +3,7 @@
 import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.BUILD_SYSTEM_PROPERTIES;
 import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEPLOYMENT_LIB;
 import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.LIB;
+import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -42,7 +43,7 @@ public static Closeable main(Path appRoot) throws Exception {
                 Files.newInputStream(appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB).resolve(JarResultBuildStep.APPMODEL_DAT)))) {
             Properties buildSystemProperties = new Properties();
             try (InputStream buildIn = Files
-                    .newInputStream(appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB).resolve(BUILD_SYSTEM_PROPERTIES))) {
+                    .newInputStream(appRoot.resolve(QUARKUS).resolve(BUILD_SYSTEM_PROPERTIES))) {
                 buildSystemProperties.load(buildIn);
             }
 
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java b/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java
index fca86f6e985..426e711b0db 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java
@@ -3,6 +3,7 @@
 import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.BUILD_SYSTEM_PROPERTIES;
 import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEPLOYMENT_LIB;
 import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.LIB;
+import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS;
 
 import java.io.InputStream;
 import java.io.ObjectInputStream;
@@ -26,11 +27,12 @@ public class ReaugmentTask {
     public static void main(Path appRoot) throws Exception {
 
         Path deploymentLib = appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB);
+        Path buildSystemProps = appRoot.resolve(QUARKUS).resolve(BUILD_SYSTEM_PROPERTIES);
         try (ObjectInputStream in = new ObjectInputStream(
                 Files.newInputStream(deploymentLib.resolve(JarResultBuildStep.APPMODEL_DAT)))) {
             Properties buildSystemProperties = new Properties();
             try (InputStream buildIn = Files
-                    .newInputStream(deploymentLib.resolve(BUILD_SYSTEM_PROPERTIES))) {
+                    .newInputStream(buildSystemProps)) {
                 buildSystemProperties.load(buildIn);
             }
 
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
index d9a75305c86..1850c6ab0f8 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java
@@ -4,6 +4,7 @@
 
 import java.io.BufferedInputStream;
 import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -11,6 +12,7 @@
 import java.io.OutputStream;
 import java.io.UncheckedIOException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileSystem;
 import java.nio.file.FileVisitOption;
@@ -704,6 +706,7 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
             if (packageConfig.type.equalsIgnoreCase(PackageConfig.MUTABLE_JAR)) {
 
                 Path deploymentLib = libDir.resolve(DEPLOYMENT_LIB);
+                Path buildSystemProps = quarkus.resolve(BUILD_SYSTEM_PROPERTIES);
                 Files.createDirectories(deploymentLib);
                 for (AppDependency appDep : curateOutcomeBuildItem.getEffectiveModel().getFullDeploymentDeps()) {
                     copyDependency(parentFirstKeys, outputTargetBuildItem, copiedArtifacts, deploymentLib, baseLib, jars,
@@ -747,9 +750,17 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
                     obj.writeObject(paths);
                     obj.close();
                 }
-                Path buildSystemProps = deploymentLib.resolve(BUILD_SYSTEM_PROPERTIES);
-                try (OutputStream out = Files.newOutputStream(buildSystemProps)) {
-                    outputTargetBuildItem.getBuildSystemProperties().store(out, "The original build properties");
+                //we output the properties in a reproducible manner, so we remove the date comment
+                //and sort them
+                //we still use Properties to get the escaping right though, so basically we write out the lines
+                //to memory, split them, discard comments, sort them, then write them to disk
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                outputTargetBuildItem.getBuildSystemProperties().store(out, null);
+                List<String> lines = Arrays.stream(new String(out.toByteArray(), StandardCharsets.UTF_8).split("\\n"))
+                        .filter(s -> !s.startsWith("#")).sorted().collect(Collectors.toList());
+
+                try (OutputStream fileOutput = Files.newOutputStream(buildSystemProps)) {
+                    fileOutput.write(String.join("\\n", lines).getBytes(StandardCharsets.UTF_8));
                 }
             }