Browse Source

Improve null-safety of build-plugin/spring-boot-gradle-plugin

See gh-46926
pull/46973/head
Moritz Halbritter 4 months ago
parent
commit
2a907ba369
  1. 10
      build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java
  2. 21
      build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java
  3. 16
      build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java
  4. 11
      build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java
  5. 9
      build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java

10
build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java

@ -17,6 +17,7 @@
package org.springframework.boot.gradle.plugin; package org.springframework.boot.gradle.plugin;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -38,6 +39,7 @@ import org.gradle.jvm.application.scripts.TemplateBasedScriptGenerator;
import org.gradle.util.GradleVersion; import org.gradle.util.GradleVersion;
import org.springframework.boot.gradle.tasks.run.BootRun; import org.springframework.boot.gradle.tasks.run.BootRun;
import org.springframework.util.Assert;
/** /**
* Action that is executed in response to the {@link ApplicationPlugin} being applied. * Action that is executed in response to the {@link ApplicationPlugin} being applied.
@ -110,7 +112,7 @@ final class ApplicationPluginAction implements PluginApplicationAction {
} }
private String loadResource(String name) { private String loadResource(String name) {
try (InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream(name))) { try (InputStreamReader reader = new InputStreamReader(getResourceAsStream(name))) {
char[] buffer = new char[4096]; char[] buffer = new char[4096];
int read; int read;
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
@ -124,6 +126,12 @@ final class ApplicationPluginAction implements PluginApplicationAction {
} }
} }
private InputStream getResourceAsStream(String name) {
InputStream stream = getClass().getResourceAsStream(name);
Assert.state(stream != null, "Resource '%s' not found'".formatted(name));
return stream;
}
private void configureFilePermissions(CopySpec copySpec, int mode) { private void configureFilePermissions(CopySpec copySpec, int mode) {
if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) { if (GradleVersion.current().compareTo(GradleVersion.version("8.3")) >= 0) {
copySpec.filePermissions((filePermissions) -> filePermissions.unix(Integer.toString(mode, 8))); copySpec.filePermissions((filePermissions) -> filePermissions.unix(Integer.toString(mode, 8)));

21
build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java

@ -48,7 +48,7 @@ public abstract class BuildInfoProperties implements Serializable {
private final SetProperty<String> excludes; private final SetProperty<String> excludes;
private final Supplier<String> creationTime = () -> DateTimeFormatter.ISO_INSTANT.format(Instant.now()); private final Supplier<@Nullable String> creationTime = () -> DateTimeFormatter.ISO_INSTANT.format(Instant.now());
@Inject @Inject
public BuildInfoProperties(Project project, SetProperty<String> excludes) { public BuildInfoProperties(Project project, SetProperty<String> excludes) {
@ -142,15 +142,19 @@ public abstract class BuildInfoProperties implements Serializable {
return coerceToStringValues(applyExclusions(getAdditional().getOrElse(Collections.emptyMap()))); return coerceToStringValues(applyExclusions(getAdditional().getOrElse(Collections.emptyMap())));
} }
@SuppressWarnings("NullAway") // Doesn't detect lambda with correct nullability
private <T> @Nullable T getIfNotExcluded(Property<T> property, String name) { private <T> @Nullable T getIfNotExcluded(Property<T> property, String name) {
return getIfNotExcluded(property, name, () -> null); return getIfNotExcluded(property, name, () -> null);
} }
private <T> @Nullable T getIfNotExcluded(Property<T> property, String name, Supplier<T> defaultValue) { private <T> @Nullable T getIfNotExcluded(Property<T> property, String name, Supplier<@Nullable T> defaultValue) {
if (this.excludes.getOrElse(Collections.emptySet()).contains(name)) { if (this.excludes.getOrElse(Collections.emptySet()).contains(name)) {
return null; return null;
} }
return property.getOrElse(defaultValue.get()); if (property.isPresent()) {
return property.get();
}
return defaultValue.get();
} }
private Map<String, String> coerceToStringValues(Map<String, Object> input) { private Map<String, String> coerceToStringValues(Map<String, Object> input) {
@ -159,7 +163,9 @@ public abstract class BuildInfoProperties implements Serializable {
if (value instanceof Provider<?> provider) { if (value instanceof Provider<?> provider) {
value = provider.getOrNull(); value = provider.getOrNull();
} }
output.put(key, (value != null) ? value.toString() : null); if (value != null) {
output.put(key, value.toString());
}
}); });
return output; return output;
} }
@ -167,7 +173,12 @@ public abstract class BuildInfoProperties implements Serializable {
private Map<String, Object> applyExclusions(Map<String, Object> input) { private Map<String, Object> applyExclusions(Map<String, Object> input) {
Map<String, Object> output = new HashMap<>(); Map<String, Object> output = new HashMap<>();
Set<String> exclusions = this.excludes.getOrElse(Collections.emptySet()); Set<String> exclusions = this.excludes.getOrElse(Collections.emptySet());
input.forEach((key, value) -> output.put(key, (!exclusions.contains(key)) ? value : null)); input.forEach((key, value) -> {
boolean isExcluded = exclusions.contains(key);
if (!isExcluded) {
output.put(key, value);
}
});
return output; return output;
} }

16
build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java

@ -41,6 +41,7 @@ import org.gradle.work.DisableCachingByDefault;
import org.springframework.boot.buildpack.platform.build.BuildRequest; import org.springframework.boot.buildpack.platform.build.BuildRequest;
import org.springframework.boot.buildpack.platform.build.Builder; import org.springframework.boot.buildpack.platform.build.Builder;
import org.springframework.boot.buildpack.platform.build.BuildpackReference; import org.springframework.boot.buildpack.platform.build.BuildpackReference;
import org.springframework.boot.buildpack.platform.build.Cache;
import org.springframework.boot.buildpack.platform.build.Creator; import org.springframework.boot.buildpack.platform.build.Creator;
import org.springframework.boot.buildpack.platform.build.PullPolicy; import org.springframework.boot.buildpack.platform.build.PullPolicy;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException; import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
@ -460,14 +461,17 @@ public abstract class BootBuildImage extends DefaultTask {
} }
private BuildRequest customizeCaches(BuildRequest request) { private BuildRequest customizeCaches(BuildRequest request) {
if (this.buildWorkspace.asCache() != null) { Cache buildWorkspaceCache = this.buildWorkspace.asCache();
request = request.withBuildWorkspace((this.buildWorkspace.asCache())); if (buildWorkspaceCache != null) {
request = request.withBuildWorkspace(buildWorkspaceCache);
} }
if (this.buildCache.asCache() != null) { Cache buildCache = this.buildCache.asCache();
request = request.withBuildCache(this.buildCache.asCache()); if (buildCache != null) {
request = request.withBuildCache(buildCache);
} }
if (this.launchCache.asCache() != null) { Cache launchCache = this.launchCache.asCache();
request = request.withLaunchCache(this.launchCache.asCache()); if (launchCache != null) {
request = request.withLaunchCache(launchCache);
} }
return request; return request;
} }

11
build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java

@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
@ -111,7 +112,7 @@ class BootZipCopyAction implements CopyAction {
private final Function<FileCopyDetails, ZipCompression> compressionResolver; private final Function<FileCopyDetails, ZipCompression> compressionResolver;
private final String encoding; private final @Nullable String encoding;
private final ResolvedDependencies resolvedDependencies; private final ResolvedDependencies resolvedDependencies;
@ -125,7 +126,7 @@ class BootZipCopyAction implements CopyAction {
@Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation, @Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation,
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions, Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
@Nullable LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec, @Nullable LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
Function<FileCopyDetails, ZipCompression> compressionResolver, String encoding, Function<FileCopyDetails, ZipCompression> compressionResolver, @Nullable String encoding,
ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile,
@Nullable LayerResolver layerResolver, LoaderImplementation loaderImplementation) { @Nullable LayerResolver layerResolver, LoaderImplementation loaderImplementation) {
this.output = output; this.output = output;
@ -390,7 +391,8 @@ class BootZipCopyAction implements CopyAction {
if (classPathIndex != null) { if (classPathIndex != null) {
Set<String> libraryNames = this.writtenLibraries.keySet(); Set<String> libraryNames = this.writtenLibraries.keySet();
List<String> lines = libraryNames.stream().map((line) -> "- \"" + line + "\"").toList(); List<String> lines = libraryNames.stream().map((line) -> "- \"" + line + "\"").toList();
ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines(BootZipCopyAction.this.encoding, lines); ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines((BootZipCopyAction.this.encoding != null)
? BootZipCopyAction.this.encoding : StandardCharsets.UTF_8.name(), lines);
writeEntry(classPathIndex, writer, true); writeEntry(classPathIndex, writer, true);
} }
} }
@ -415,7 +417,8 @@ class BootZipCopyAction implements CopyAction {
} }
NativeImageArgFile argFile = new NativeImageArgFile(excludes); NativeImageArgFile argFile = new NativeImageArgFile(excludes);
argFile.writeIfNecessary((lines) -> { argFile.writeIfNecessary((lines) -> {
ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines(BootZipCopyAction.this.encoding, lines); ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines((BootZipCopyAction.this.encoding != null)
? BootZipCopyAction.this.encoding : StandardCharsets.UTF_8.name(), lines);
writeEntry(NativeImageArgFile.LOCATION, writer, true); writeEntry(NativeImageArgFile.LOCATION, writer, true);
}); });
} }

9
build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java

@ -30,6 +30,7 @@ import org.gradle.api.file.FileTreeElement;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.LoaderImplementation;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
/** /**
@ -61,7 +62,7 @@ class LoaderZipEntries {
WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException {
WrittenEntries written = new WrittenEntries(); WrittenEntries written = new WrittenEntries();
try (ZipInputStream loaderJar = new ZipInputStream( try (ZipInputStream loaderJar = new ZipInputStream(
getClass().getResourceAsStream("/" + this.loaderImplementation.getJarResourceName()))) { getResourceAsStream("/" + this.loaderImplementation.getJarResourceName()))) {
java.util.zip.ZipEntry entry = loaderJar.getNextEntry(); java.util.zip.ZipEntry entry = loaderJar.getNextEntry();
while (entry != null) { while (entry != null) {
if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) {
@ -78,6 +79,12 @@ class LoaderZipEntries {
return written; return written;
} }
private InputStream getResourceAsStream(String name) {
InputStream stream = getClass().getResourceAsStream(name);
Assert.state(stream != null, "Resource '%s not found'".formatted(name));
return stream;
}
private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException { private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException {
prepareEntry(entry, this.dirMode); prepareEntry(entry, this.dirMode);
out.putArchiveEntry(entry); out.putArchiveEntry(entry);

Loading…
Cancel
Save