Write native-image argfile only if there are excludes

Refactors duplicate logic in BootZipCopyAction and Packager into
separate classes.

Closes gh-33363

Co-authored-by: Phillip Webb <pwebb@vmware.com>
pull/33424/head
Moritz Halbritter 2 years ago
parent 276b288891
commit c6536c54d8

@ -22,7 +22,6 @@ 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.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -31,7 +30,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -59,6 +57,8 @@ import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.LayersIndex; import org.springframework.boot.loader.tools.LayersIndex;
import org.springframework.boot.loader.tools.LibraryCoordinates; import org.springframework.boot.loader.tools.LibraryCoordinates;
import org.springframework.boot.loader.tools.NativeImageArgFile;
import org.springframework.boot.loader.tools.ReachabilityMetadataProperties;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -76,10 +76,9 @@ class BootZipCopyAction implements CopyAction {
static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0) static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0)
.getTimeInMillis(); .getTimeInMillis();
private static final String REACHABILITY_METADATA_PROPERTIES_LOCATION = "META-INF/native-image/%s/%s/%s/reachability-metadata.properties";
private static final Pattern REACHABILITY_METADATA_PROPERTIES_LOCATION_PATTERN = Pattern private static final Pattern REACHABILITY_METADATA_PROPERTIES_LOCATION_PATTERN = Pattern
.compile(REACHABILITY_METADATA_PROPERTIES_LOCATION.formatted(".*", ".*", ".*")); .compile(ReachabilityMetadataProperties.REACHABILITY_METADATA_PROPERTIES_LOCATION_TEMPLATE.formatted(".*",
".*", ".*"));
private final File output; private final File output;
@ -355,32 +354,23 @@ class BootZipCopyAction implements CopyAction {
DependencyDescriptor descriptor = BootZipCopyAction.this.resolvedDependencies DependencyDescriptor descriptor = BootZipCopyAction.this.resolvedDependencies
.find(entry.getValue().getFile()); .find(entry.getValue().getFile());
LibraryCoordinates coordinates = (descriptor != null) ? descriptor.getCoordinates() : null; LibraryCoordinates coordinates = (descriptor != null) ? descriptor.getCoordinates() : null;
FileCopyDetails propertiesFile = (coordinates != null) FileCopyDetails propertiesFile = (coordinates != null) ? this.reachabilityMetadataProperties
? this.reachabilityMetadataProperties.get(REACHABILITY_METADATA_PROPERTIES_LOCATION.formatted( .get(ReachabilityMetadataProperties.getLocation(coordinates)) : null;
coordinates.getGroupId(), coordinates.getArtifactId(), coordinates.getVersion()))
: null;
if (propertiesFile != null) { if (propertiesFile != null) {
try (InputStream inputStream = propertiesFile.open()) { try (InputStream inputStream = propertiesFile.open()) {
Properties properties = new Properties(); ReachabilityMetadataProperties properties = ReachabilityMetadataProperties
properties.load(inputStream); .fromInputStream(inputStream);
if (Boolean.parseBoolean(properties.getProperty("override"))) { if (properties.isOverridden()) {
excludes.add(entry.getKey()); excludes.add(entry.getKey());
} }
} }
} }
} }
if (excludes != null) { NativeImageArgFile argFile = new NativeImageArgFile(excludes);
List<String> args = new ArrayList<>(); argFile.writeIfNecessary((lines) -> {
for (String exclude : excludes) { ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines(BootZipCopyAction.this.encoding, lines);
int lastSlash = exclude.lastIndexOf('/'); writeEntry(NativeImageArgFile.LOCATION, writer, true);
String jar = (lastSlash != -1) ? exclude.substring(lastSlash + 1) : exclude; });
args.add("--exclude-config");
args.add(Pattern.quote(jar));
args.add("^/META-INF/native-image/.*");
}
ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines(BootZipCopyAction.this.encoding, args);
writeEntry("META-INF/native-image/argfile", writer, true);
}
} }
private void writeLayersIndexIfNecessary() throws IOException { private void writeLayersIndexIfNecessary() throws IOException {

@ -201,6 +201,13 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
} }
} }
@Test
void nativeImageArgFileIsNotWrittenWhenExcludesAreEmpty() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar(false))) {
assertThat(jarFile.getEntry("META-INF/native-image/argfile")).isNull();
}
}
@Test @Test
void javaVersionIsWrittenToManifest() throws IOException { void javaVersionIsWrittenToManifest() throws IOException {
try (JarFile jarFile = new JarFile(createPopulatedJar())) { try (JarFile jarFile = new JarFile(createPopulatedJar())) {

@ -0,0 +1,69 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.loader.tools;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import org.springframework.util.function.ThrowingConsumer;
/**
* Class to work with the native-image argfile.
*
* @author Moritz Halbritter
* @author Phil Webb
* @since 3.0.0
*/
public final class NativeImageArgFile {
/**
* Location of the argfile.
*/
public static final String LOCATION = "META-INF/native-image/argfile";
private final List<String> excludes;
/**
* Constructs a new instance with the given excludes.
* @param excludes dependencies for which the reachability metadata should be excluded
*/
public NativeImageArgFile(Collection<String> excludes) {
this.excludes = List.copyOf(excludes);
}
/**
* Write the arguments file if it is necessary.
* @param writer consumer that should write the contents
*/
public void writeIfNecessary(ThrowingConsumer<List<String>> writer) {
if (this.excludes.isEmpty()) {
return;
}
List<String> lines = new ArrayList<>();
for (String exclude : this.excludes) {
int lastSlash = exclude.lastIndexOf('/');
String jar = (lastSlash != -1) ? exclude.substring(lastSlash + 1) : exclude;
lines.add("--exclude-config");
lines.add(Pattern.quote(jar));
lines.add("^/META-INF/native-image/.*");
}
writer.accept(lines);
}
}

@ -28,7 +28,6 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -37,8 +36,6 @@ import java.util.jar.Attributes;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry; import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
@ -61,8 +58,6 @@ import org.springframework.util.StringUtils;
*/ */
public abstract class Packager { public abstract class Packager {
private static final String REACHABILITY_METADATA_PROPERTIES_LOCATION = "META-INF/native-image/%s/%s/%s/reachability-metadata.properties";
private static final String MAIN_CLASS_ATTRIBUTE = "Main-Class"; private static final String MAIN_CLASS_ATTRIBUTE = "Main-Class";
private static final String START_CLASS_ATTRIBUTE = "Start-Class"; private static final String START_CLASS_ATTRIBUTE = "Start-Class";
@ -229,31 +224,24 @@ public abstract class Packager {
Set<String> excludes = new LinkedHashSet<>(); Set<String> excludes = new LinkedHashSet<>();
for (Map.Entry<String, Library> entry : writtenLibraries.entrySet()) { for (Map.Entry<String, Library> entry : writtenLibraries.entrySet()) {
LibraryCoordinates coordinates = entry.getValue().getCoordinates(); LibraryCoordinates coordinates = entry.getValue().getCoordinates();
ZipEntry zipEntry = (coordinates != null) ? sourceJar.getEntry(REACHABILITY_METADATA_PROPERTIES_LOCATION ZipEntry zipEntry = (coordinates != null)
.formatted(coordinates.getGroupId(), coordinates.getArtifactId(), coordinates.getVersion())) : null; ? sourceJar.getEntry(ReachabilityMetadataProperties.getLocation(coordinates)) : null;
if (zipEntry != null) { if (zipEntry != null) {
try (InputStream inputStream = sourceJar.getInputStream(zipEntry)) { try (InputStream inputStream = sourceJar.getInputStream(zipEntry)) {
Properties properties = new Properties(); ReachabilityMetadataProperties properties = ReachabilityMetadataProperties
properties.load(inputStream); .fromInputStream(inputStream);
if (Boolean.parseBoolean(properties.getProperty("override"))) { if (properties.isOverridden()) {
excludes.add(entry.getKey()); excludes.add(entry.getKey());
} }
} }
} }
} }
if (!excludes.isEmpty()) { NativeImageArgFile argFile = new NativeImageArgFile(excludes);
List<String> args = new ArrayList<>(); argFile.writeIfNecessary((lines) -> {
for (String exclude : excludes) { String contents = String.join("\n", lines) + "\n";
int lastSlash = exclude.lastIndexOf('/'); writer.writeEntry(NativeImageArgFile.LOCATION,
String jar = (lastSlash != -1) ? exclude.substring(lastSlash + 1) : exclude;
args.add("--exclude-config");
args.add(Pattern.quote(jar));
args.add("^/META-INF/native-image/.*");
}
String contents = args.stream().collect(Collectors.joining("\n")) + "\n";
writer.writeEntry("META-INF/native-image/argfile",
new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))); new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)));
} });
} }
private void writeLayerIndex(AbstractJarWriter writer) throws IOException { private void writeLayerIndex(AbstractJarWriter writer) throws IOException {

@ -0,0 +1,75 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.loader.tools;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Class to work with {@code reachability-metadata.properties}.
*
* @author Moritz Halbritter
* @since 3.0.0
*/
public final class ReachabilityMetadataProperties {
/**
* Location of the properties file. Must be formatted using
* {@link String#format(String, Object...)} with the group id, artifact id and version
* of the dependency.
*/
public static final String REACHABILITY_METADATA_PROPERTIES_LOCATION_TEMPLATE = "META-INF/native-image/%s/%s/%s/reachability-metadata.properties";
private final Properties properties;
private ReachabilityMetadataProperties(Properties properties) {
this.properties = properties;
}
/**
* Returns if the dependency has been overridden.
* @return true if the dependency has been overridden
*/
public boolean isOverridden() {
return Boolean.parseBoolean(this.properties.getProperty("override"));
}
/**
* Constructs a new instance from the given {@code InputStream}.
* @param inputStream {@code InputStream} to load the properties from
* @return loaded properties
* @throws IOException if loading from the {@code InputStream} went wrong
*/
public static ReachabilityMetadataProperties fromInputStream(InputStream inputStream) throws IOException {
Properties properties = new Properties();
properties.load(inputStream);
return new ReachabilityMetadataProperties(properties);
}
/**
* Returns the location of the properties for the given coordinates.
* @param coordinates library coordinates for which the property file location should
* be returned
* @return location of the properties
*/
public static String getLocation(LibraryCoordinates coordinates) {
return REACHABILITY_METADATA_PROPERTIES_LOCATION_TEMPLATE.formatted(coordinates.getGroupId(),
coordinates.getArtifactId(), coordinates.getVersion());
}
}

@ -0,0 +1,50 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.loader.tools;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* Tests for @{link NativeImageArgFile}.
*
* @author Moritz Halbritter
*/
class NativeImageArgFileTests {
@Test
void writeIfNecessaryWhenHasExcludesWritesLines() {
NativeImageArgFile argFile = new NativeImageArgFile(List.of("path/to/dependency-1.jar", "dependency-2.jar"));
List<String> lines = new ArrayList<>();
argFile.writeIfNecessary(lines::addAll);
assertThat(lines).containsExactly("--exclude-config", "\\Qdependency-1.jar\\E", "^/META-INF/native-image/.*",
"--exclude-config", "\\Qdependency-2.jar\\E", "^/META-INF/native-image/.*");
}
@Test
void writeIfNecessaryWhenHasNothingDoesNotCallConsumer() {
NativeImageArgFile argFile = new NativeImageArgFile(Collections.emptyList());
argFile.writeIfNecessary((lines) -> fail("Should not be called"));
}
}

@ -0,0 +1,50 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.loader.tools;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ReachabilityMetadataProperties}.
*
* @author Moritz Halbritter
*/
class ReachabilityMetadataPropertiesTests {
@Test
void shouldReadFromInputStream() throws IOException {
String propertiesContent = "override=true\n";
ReachabilityMetadataProperties properties = ReachabilityMetadataProperties
.fromInputStream(new ByteArrayInputStream(propertiesContent.getBytes(StandardCharsets.UTF_8)));
assertThat(properties.isOverridden()).isTrue();
}
@Test
void shouldFormatLocation() {
String location = ReachabilityMetadataProperties
.getLocation(LibraryCoordinates.of("group-id", "artifact-id", "1.0.0"));
assertThat(location)
.isEqualTo("META-INF/native-image/group-id/artifact-id/1.0.0/reachability-metadata.properties");
}
}
Loading…
Cancel
Save