Skip to content

Commit

Permalink
Gradle-plugin: properly handle read-only files
Browse files Browse the repository at this point in the history
AppCDS files (`app-cds-jsa`) are created as read-only, which lets
Gradle's copy+sync file operations fail, if the target file already
exists (as read-only). Similar for Gradle's delete file system
operation.

This change handles this case by deleting existing target files that are
read-only.

Fixes #33842
  • Loading branch information
snazy committed Jun 8, 2023
1 parent 0db1710 commit 5ee63a0
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 5ee63a0

Please sign in to comment.