Skip to content

Commit

Permalink
Write native-image argfile only if there are excludes
Browse files Browse the repository at this point in the history
Refactors duplicate logic in BootZipCopyAction and Packager into
separate classes.

Closes gh-33363

Co-authored-by: Phillip Webb <pwebb@vmware.com>
  • Loading branch information
mhalbritter and philwebb committed Nov 30, 2022
1 parent 276b288 commit c6536c5
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 46 deletions.
Expand Up @@ -22,7 +22,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
Expand All @@ -31,7 +30,6 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -59,6 +57,8 @@
import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.LayersIndex;
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.StreamUtils;
import org.springframework.util.StringUtils;
Expand All @@ -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)
.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
.compile(REACHABILITY_METADATA_PROPERTIES_LOCATION.formatted(".*", ".*", ".*"));
.compile(ReachabilityMetadataProperties.REACHABILITY_METADATA_PROPERTIES_LOCATION_TEMPLATE.formatted(".*",
".*", ".*"));

private final File output;

Expand Down Expand Up @@ -355,32 +354,23 @@ private void writeNativeImageArgFileIfNecessary() throws IOException {
DependencyDescriptor descriptor = BootZipCopyAction.this.resolvedDependencies
.find(entry.getValue().getFile());
LibraryCoordinates coordinates = (descriptor != null) ? descriptor.getCoordinates() : null;
FileCopyDetails propertiesFile = (coordinates != null)
? this.reachabilityMetadataProperties.get(REACHABILITY_METADATA_PROPERTIES_LOCATION.formatted(
coordinates.getGroupId(), coordinates.getArtifactId(), coordinates.getVersion()))
: null;
FileCopyDetails propertiesFile = (coordinates != null) ? this.reachabilityMetadataProperties
.get(ReachabilityMetadataProperties.getLocation(coordinates)) : null;
if (propertiesFile != null) {
try (InputStream inputStream = propertiesFile.open()) {
Properties properties = new Properties();
properties.load(inputStream);
if (Boolean.parseBoolean(properties.getProperty("override"))) {
ReachabilityMetadataProperties properties = ReachabilityMetadataProperties
.fromInputStream(inputStream);
if (properties.isOverridden()) {
excludes.add(entry.getKey());
}
}
}
}
if (excludes != null) {
List<String> args = new ArrayList<>();
for (String exclude : excludes) {
int lastSlash = exclude.lastIndexOf('/');
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);
}
NativeImageArgFile argFile = new NativeImageArgFile(excludes);
argFile.writeIfNecessary((lines) -> {
ZipEntryContentWriter writer = ZipEntryContentWriter.fromLines(BootZipCopyAction.this.encoding, lines);
writeEntry(NativeImageArgFile.LOCATION, writer, true);
});
}

private void writeLayersIndexIfNecessary() throws IOException {
Expand Down
Expand Up @@ -201,6 +201,13 @@ void nativeImageArgFileWithExcludesIsWritten() throws IOException {
}
}

@Test
void nativeImageArgFileIsNotWrittenWhenExcludesAreEmpty() throws IOException {
try (JarFile jarFile = new JarFile(createLayeredJar(false))) {
assertThat(jarFile.getEntry("META-INF/native-image/argfile")).isNull();
}
}

@Test
void javaVersionIsWrittenToManifest() throws IOException {
try (JarFile jarFile = new JarFile(createPopulatedJar())) {
Expand Down
@@ -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);
}

}
Expand Up @@ -28,7 +28,6 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
Expand All @@ -37,8 +36,6 @@
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;

import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
Expand All @@ -61,8 +58,6 @@
*/
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 START_CLASS_ATTRIBUTE = "Start-Class";
Expand Down Expand Up @@ -229,31 +224,24 @@ private void writeNativeImageArgFile(AbstractJarWriter writer, JarFile sourceJar
Set<String> excludes = new LinkedHashSet<>();
for (Map.Entry<String, Library> entry : writtenLibraries.entrySet()) {
LibraryCoordinates coordinates = entry.getValue().getCoordinates();
ZipEntry zipEntry = (coordinates != null) ? sourceJar.getEntry(REACHABILITY_METADATA_PROPERTIES_LOCATION
.formatted(coordinates.getGroupId(), coordinates.getArtifactId(), coordinates.getVersion())) : null;
ZipEntry zipEntry = (coordinates != null)
? sourceJar.getEntry(ReachabilityMetadataProperties.getLocation(coordinates)) : null;
if (zipEntry != null) {
try (InputStream inputStream = sourceJar.getInputStream(zipEntry)) {
Properties properties = new Properties();
properties.load(inputStream);
if (Boolean.parseBoolean(properties.getProperty("override"))) {
ReachabilityMetadataProperties properties = ReachabilityMetadataProperties
.fromInputStream(inputStream);
if (properties.isOverridden()) {
excludes.add(entry.getKey());
}
}
}
}
if (!excludes.isEmpty()) {
List<String> args = new ArrayList<>();
for (String exclude : excludes) {
int lastSlash = exclude.lastIndexOf('/');
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",
NativeImageArgFile argFile = new NativeImageArgFile(excludes);
argFile.writeIfNecessary((lines) -> {
String contents = String.join("\n", lines) + "\n";
writer.writeEntry(NativeImageArgFile.LOCATION,
new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)));
}
});
}

private void writeLayerIndex(AbstractJarWriter writer) throws IOException {
Expand Down
@@ -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 comments on commit c6536c5

Please sign in to comment.