Repository: quarkusio/quarkus
Issue: AppCDS generation failed with `Permission denied`

### Describe the bug

Building application with AppCDS `gradle build -Dquarkus.package.create-appcds=true` failed with error
```
   > Could not copy file '/home/palo/git/app-cds-fail/build/quarkus-build/app/quarkus-app/app-cds.jsa' to '/home/palo/git/app-cds-fail/build/quarkus-app/app-cds.jsa'.
   > /home/palo/git/app-cds-fail/build/quarkus-app/app-cds.jsa (Permission denied)
```


### Expected behavior

AppCDS generation success.

### Actual behavior

_No response_

### How to Reproduce?

Reproducer: https://github.com/paloliska/app-cds-fail

1. build with `gradle build -Dquarkus.package.create-appcds=true`

### Output of `uname -a` or `ver`

Linux *** 6.1.0-1013-oem #13-Ubuntu SMP PREEMPT_DYNAMIC Thu May 18 16:45:09 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

### Output of `java -version`

openjdk version "17.0.7" 2023-04-18 OpenJDK Runtime Environment (build 17.0.7+7-Ubuntu-0ubuntu122.04.2) OpenJDK 64-Bit Server VM (build 17.0.7+7-Ubuntu-0ubuntu122.04.2, mixed mode, sharing)

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

3.1.0.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

8.1.1

### Additional information

_No response_

Changed files:
devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java
devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java

diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
index 8f9a34bc20c..a2dcbb502b1 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
@@ -308,6 +308,7 @@ private void assembleFastJar() {
         getLogger().info("Synchronizing Quarkus build for {} packaging from {} and {} into {}", packageType(),
                 appBuildDir, depBuildDir, appTargetDir);
         getFileSystemOperations().sync(sync -> {
+            sync.eachFile(new CopyActionDeleteNonWriteableTarget(appTargetDir.toPath()));
             sync.into(appTargetDir);
             sync.from(appBuildDir, depBuildDir);
         });
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java
index fa954cbed2a..ec21628c731 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java
@@ -1,14 +1,18 @@
 package io.quarkus.gradle.tasks;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileCopyDetails;
 import org.gradle.api.file.FileSystemOperations;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.tasks.Classpath;
@@ -168,6 +172,9 @@ void generateBuild() {
         PackageConfig.BuiltInType packageType = packageType();
         getLogger().info("Building Quarkus app for package type {} in {}", packageType, genDir);
 
+        // Need to delete app-cds.jsa specially, because it's usually read-only and Gradle's delete file-system
+        // operation doesn't delete "read only" files :(
+        deleteFileIfExists(genDir.resolve(outputDirectory()).resolve("app-cds.jsa"));
         getFileSystemOperations().delete(delete -> {
             // Caching and "up-to-date" checks depend on the inputs, this 'delete()' should ensure that the up-to-date
             // checks work against "clean" outputs, considering that the outputs depend on the package-type.
@@ -223,6 +230,7 @@ void generateBuild() {
         getFileSystemOperations().copy(copy -> {
             copy.from(buildDir);
             copy.into(genDir);
+            copy.eachFile(new CopyActionDeleteNonWriteableTarget(genDir));
             switch (packageType) {
                 case NATIVE:
                     copy.include(nativeRunnerFileName());
@@ -261,4 +269,32 @@ void abort(String message, Object... args) {
                 });
         throw new StopExecutionException();
     }
+
+    public static final class CopyActionDeleteNonWriteableTarget implements Action<FileCopyDetails> {
+        private final Path destDir;
+
+        public CopyActionDeleteNonWriteableTarget(Path destDir) {
+            this.destDir = destDir;
+        }
+
+        @Override
+        public void execute(FileCopyDetails details) {
+            // Delete a pre-existing non-writeable file, otherwise a copy or sync operation would fail.
+            // This situation happens for 'app-cds.jsa' files, which are created as "read only" files,
+            // prefer to keep those files read-only.
+
+            Path destFile = destDir.resolve(details.getPath());
+            if (Files.exists(destFile) && !Files.isWritable(destFile)) {
+                deleteFileIfExists(destFile);
+            }
+        }
+    }
+
+    protected static void deleteFileIfExists(Path file) {
+        try {
+            Files.deleteIfExists(file);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java
index 94a82fc82ac..3a71110e64d 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java
@@ -15,6 +15,7 @@
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.tasks.CacheableTask;
 import org.gradle.api.tasks.CompileClasspath;
+import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.PathSensitive;
@@ -62,6 +63,11 @@ public void setCompileClasspath(Configuration compileClasspath) {
         this.compileClasspath = compileClasspath;
     }
 
+    @Input
+    public Map<String, String> getCachingRelevantInput() {
+        return extension().baseConfig().quarkusProperties();
+    }
+
     @InputFiles
     @PathSensitive(PathSensitivity.RELATIVE)
     public Set<File> getInputDirectory() {