Skip to content

Commit 6d72c6a

Browse files
committed
[GR-55581] Check layered image build option compatibility.
PullRequest: graal/20725
2 parents be2d8fe + 8457742 commit 6d72c6a

File tree

18 files changed

+743
-321
lines changed

18 files changed

+743
-321
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/BuildArtifacts.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ enum ArtifactType {
6868
BUILD_INFO("build_info"),
6969
/* For all debugging-related artifacts. */
7070
DEBUG_INFO("debug_info"),
71-
LAYER_SNAPSHOT("layer_snapshot"),
72-
LAYER_SNAPSHOT_GRAPHS("layer_snapshot_graphs"),
7371

7472
/* For C header files. */
7573
C_HEADER("c_headers"),

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/NativeImageClassLoaderOptions.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,37 @@
2424
*/
2525
package com.oracle.svm.core;
2626

27-
import jdk.graal.compiler.options.Option;
28-
2927
import com.oracle.svm.core.option.APIOption;
30-
import com.oracle.svm.core.option.HostedOptionKey;
3128
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
29+
import com.oracle.svm.core.option.HostedOptionKey;
30+
import com.oracle.svm.core.option.LayerVerifiedOption;
31+
import com.oracle.svm.core.option.LayerVerifiedOption.Kind;
32+
import com.oracle.svm.core.option.LayerVerifiedOption.Severity;
33+
34+
import jdk.graal.compiler.options.Option;
3235

3336
public class NativeImageClassLoaderOptions {
3437
public static final String AddExportsAndOpensFormat = "<module>/<package>=<target-module>(,<target-module>)*";
3538
public static final String AddReadsFormat = "<module>=<target-module>(,<target-module>)*";
3639

40+
@LayerVerifiedOption(kind = Kind.Removed, severity = Severity.Error, positional = false)//
3741
@APIOption(name = "add-exports", extra = true, launcherOption = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})//
3842
@Option(help = "Value " + AddExportsAndOpensFormat + " updates <module> to export <package> to <target-module>, regardless of module declaration." +
3943
" <target-module> can be ALL-UNNAMED to export to all unnamed modules.")//
4044
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> AddExports = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
4145

46+
@LayerVerifiedOption(kind = Kind.Removed, severity = Severity.Error, positional = false)//
4247
@APIOption(name = "add-opens", extra = true, launcherOption = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})//
4348
@Option(help = "Value " + AddExportsAndOpensFormat + " updates <module> to open <package> to <target-module>, regardless of module declaration.")//
4449
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> AddOpens = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
4550

51+
@LayerVerifiedOption(kind = Kind.Removed, severity = Severity.Error, positional = false)//
4652
@APIOption(name = "add-reads", extra = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})//
4753
@Option(help = "Value " + AddReadsFormat + " updates <module> to read <target-module>, regardless of module declaration." +
4854
" <target-module> can be ALL-UNNAMED to read all unnamed modules.")//
4955
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> AddReads = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
5056

57+
@LayerVerifiedOption(kind = Kind.Removed, severity = Severity.Error)//
5158
@APIOption(name = "enable-native-access", launcherOption = true, valueSeparator = {APIOption.WHITESPACE_SEPARATOR, '='})//
5259
@Option(help = "A comma-separated list of modules that are permitted to perform restricted native operations." +
5360
" The module name can also be ALL-UNNAMED.")//

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 47 additions & 0 deletions
Large diffs are not rendered by default.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,25 @@ native-image --module-path target/AwesomeLib-1.0-SNAPSHOT.jar --shared
186186
3. The layer specified by `--layer-use` must be compatible with the standalone command line.
187187
The compatibility rules refer to:
188188
- class/jar file compatibility (`-cp`, `-p`, same JDK, same GraalVM, same libs, etc.)
189-
- config compatibility: GC config, etc.
189+
- config compatibility: image builder options (e.g. GC config), etc.
190190
- Java system properties and Environment variables compatibility
191191
- access compatibility: no additional unsafe and field accesses are allowed
192192

193193
In case of incompatibility an error message is printed and the build process is aborted.
194+
More information about compatibility checking can be found [below](#compatibility-rules)
195+
196+
### Compatibility rules
197+
198+
Currently, layer build compatibility checking is very limited and is only performed for the image builder arguments.
199+
The list below gives a few examples of checks that are already implemented.
200+
201+
- Module system options `--add-exports`, `--add-opens`, `--add-reads` that were passed in the previous image build also need to be passed in the current image build.
202+
Note that additional module system options not found in the previous image build are allowed to be used in the current image build.
203+
- Builder options of the form `-H:NeverInline=<pattern>` follow the same logic as the module system options above.
204+
- If debug option `-g` was passed in the previous image build it must also be passed in the current image build at the same position.
205+
- Other options like `-H:EntryPointNamePrefix=...`, `-H:APIFunctionPrefix=...`, ... follow the same logic as the `-g` option.
206+
207+
The number of checks is subject to change and will be further improved in the future.
194208

195209
### Limitations
196210

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.oracle.svm.core.option;
27+
28+
import java.lang.annotation.ElementType;
29+
import java.lang.annotation.Repeatable;
30+
import java.lang.annotation.Retention;
31+
import java.lang.annotation.RetentionPolicy;
32+
import java.lang.annotation.Target;
33+
34+
/**
35+
* Use this annotation to mark image builder {@link HostedOptionKey}s and {@link RuntimeOptionKey}s
36+
* that require layered image build compatibility checking. For example, if a {@code HostedOption}
37+
* set to value {@code A} in the previous layer is required to also be set to the same value in the
38+
* current layer, the respective option can use this annotation to enforce this requirement.
39+
* Annotation elements {@code Severity}, {@code Kind}, {@code Positional} and {@code Message} can be
40+
* used to configure the nature of checking that is required for the annotated option.
41+
*
42+
* @see LayerVerifiedOption.List
43+
*/
44+
@Repeatable(LayerVerifiedOption.List.class)
45+
@Retention(RetentionPolicy.RUNTIME)
46+
@Target({ElementType.FIELD})
47+
public @interface LayerVerifiedOption {
48+
49+
@Retention(RetentionPolicy.RUNTIME)
50+
@Target({ElementType.FIELD})
51+
@interface List {
52+
LayerVerifiedOption[] value();
53+
}
54+
55+
/**
56+
* Setting this element is required. It controls how a violation should be reported. Setting it
57+
* to {@code Error} will cause the image build to abort with an error message in case of a
58+
* violation. {@code Warn} reports the same message, but only as a warning, still allowing the
59+
* image build to continue.
60+
*/
61+
Severity severity();
62+
63+
enum Severity {
64+
Error,
65+
Warn
66+
}
67+
68+
/**
69+
* When a violation is detected, a generic message for reporting the violation is automatically
70+
* created. This annotation element allows to provide a message more specific to the annotated
71+
* option.
72+
*/
73+
String message() default "";
74+
75+
/**
76+
* The violation checking has three different variants to choose from. This annotation element
77+
* is used to specify which kind is requested. Setting it to {@code Removed} reports a violation
78+
* if an option was given in the previous layer build, but is missing in the current layer
79+
* build. {@code Added} reports a violation if an option is specified in the current layer build
80+
* but was not also used when the previous layer got built. {@code Changed} is the strictest
81+
* form and requires an option to always be exactly the same between dependent layers.
82+
*/
83+
Kind kind();
84+
85+
enum Kind {
86+
Removed,
87+
Changed,
88+
Added
89+
}
90+
91+
/**
92+
* The verification usually takes the position of the option within the sequence of options into
93+
* account. For some options we can be less strict and allow the checking to be independent of
94+
* the position. Specifying {@code false} selects this less strict mode. For example, if a
95+
* previous layer was built with {@code --add-exports=foo/bar=ALL-UNNAMED}, the exact position
96+
* where the current layer build specifies the same option is irrelevant as long as it does
97+
* specify it somewhere in its sequence of options.
98+
*/
99+
boolean positional() default true;
100+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ArchiveSupport.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.Map;
4141
import java.util.Properties;
4242
import java.util.concurrent.atomic.AtomicBoolean;
43+
import java.util.function.BooleanSupplier;
4344
import java.util.function.Function;
4445
import java.util.function.Predicate;
4546
import java.util.jar.Attributes;
@@ -96,15 +97,15 @@ public Manifest createManifest(String mainClass) {
9697
return mf;
9798
}
9899

99-
public void expandJarToDir(Path inputJarFilePath, Path outputDir, AtomicBoolean outputDirDeleted) {
100-
expandJarToDir(Function.identity(), inputJarFilePath, outputDir, outputDirDeleted);
100+
public void expandJarToDir(Path inputJarFilePath, Path outputDir) {
101+
expandJarToDir(Function.identity(), inputJarFilePath, outputDir, () -> false);
101102
}
102103

103-
public void expandJarToDir(Function<Path, Path> relativizeEntry, Path inputJarFilePath, Path outputDir, AtomicBoolean outputDirDeleted) {
104+
public void expandJarToDir(Function<Path, Path> relativizeEntry, Path inputJarFilePath, Path outputDir, BooleanSupplier outputDirDeleted) {
104105
try {
105106
try (JarFile archive = new JarFile(inputJarFilePath.toFile())) {
106107
Enumeration<JarEntry> jarEntries = archive.entries();
107-
while (jarEntries.hasMoreElements() && !outputDirDeleted.get()) {
108+
while (jarEntries.hasMoreElements() && !outputDirDeleted.getAsBoolean()) {
108109
JarEntry jarEntry = jarEntries.nextElement();
109110
Path originalEntry = outputDir.resolve(jarEntry.getName());
110111
Path targetEntry = relativizeEntry.apply(originalEntry);

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/BundleSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ private BundleSupport(NativeImage nativeImage, String bundleFilenameArg) {
296296
outputDir = rootDir.resolve("output");
297297

298298
Path bundleFilePath = bundlePath.resolve(bundleName + BUNDLE_FILE_EXTENSION);
299-
nativeImage.archiveSupport().expandJarToDir(e -> relativizeBundleEntry(getOriginalOutputDirName(), e), bundleFilePath, rootDir, deleteBundleRoot);
299+
nativeImage.archiveSupport().expandJarToDir(e -> relativizeBundleEntry(getOriginalOutputDirName(), e), bundleFilePath, rootDir, deleteBundleRoot::get);
300300

301301
if (deleteBundleRoot.get()) {
302302
/* Abort image build request without error message and exit with 0 */

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -515,21 +515,23 @@ public void run(Map<Method, CEntryPointData> entryPoints,
515515
setSystemPropertiesForImageLate(k);
516516

517517
var hostedOptionValues = new HostedOptionValues(optionProvider.getHostedValues());
518-
HostedImageLayerBuildingSupport imageLayerSupport = HostedImageLayerBuildingSupport.initialize(hostedOptionValues, loader);
519-
ImageSingletonsSupportImpl.HostedManagement.install(new ImageSingletonsSupportImpl.HostedManagement(imageLayerSupport.buildingImageLayer), imageLayerSupport);
520-
521-
ImageSingletons.add(LayeredImageSingletonSupport.class, (LayeredImageSingletonSupport) ImageSingletonsSupportImpl.get());
522-
ImageSingletons.add(ProgressReporter.class, reporter);
523-
ImageSingletons.add(DeadlockWatchdog.class, loader.watchdog);
524-
ImageSingletons.add(TimerCollection.class, timerCollection);
525-
ImageSingletons.add(ImageBuildStatistics.TimerCollectionPrinter.class, timerCollection);
526-
ImageSingletons.add(AnnotationExtractor.class, loader.classLoaderSupport.annotationExtractor);
527-
ImageSingletons.add(BuildArtifacts.class, new BuildArtifactsImpl());
528-
ImageSingletons.add(HostedOptionValues.class, hostedOptionValues);
529-
ImageSingletons.add(RuntimeOptionValues.class, new RuntimeOptionValues(optionProvider.getRuntimeValues(), allOptionNames));
530-
531-
try (TemporaryBuildDirectoryProviderImpl tempDirectoryProvider = new TemporaryBuildDirectoryProviderImpl()) {
518+
var tempDirectoryOptionValue = NativeImageOptions.TempDirectory.getValue(hostedOptionValues).lastValue().orElse(null);
519+
try (TemporaryBuildDirectoryProviderImpl tempDirectoryProvider = new TemporaryBuildDirectoryProviderImpl(tempDirectoryOptionValue)) {
520+
var builderTempDir = tempDirectoryProvider.getTemporaryBuildDirectory();
521+
HostedImageLayerBuildingSupport imageLayerSupport = HostedImageLayerBuildingSupport.initialize(hostedOptionValues, loader, builderTempDir);
522+
ImageSingletonsSupportImpl.HostedManagement.install(new ImageSingletonsSupportImpl.HostedManagement(imageLayerSupport.buildingImageLayer), imageLayerSupport);
523+
524+
ImageSingletons.add(LayeredImageSingletonSupport.class, (LayeredImageSingletonSupport) ImageSingletonsSupportImpl.get());
525+
ImageSingletons.add(ProgressReporter.class, reporter);
526+
ImageSingletons.add(DeadlockWatchdog.class, loader.watchdog);
527+
ImageSingletons.add(TimerCollection.class, timerCollection);
528+
ImageSingletons.add(ImageBuildStatistics.TimerCollectionPrinter.class, timerCollection);
529+
ImageSingletons.add(AnnotationExtractor.class, loader.classLoaderSupport.annotationExtractor);
530+
ImageSingletons.add(BuildArtifacts.class, new BuildArtifactsImpl());
531+
ImageSingletons.add(HostedOptionValues.class, hostedOptionValues);
532+
ImageSingletons.add(RuntimeOptionValues.class, new RuntimeOptionValues(optionProvider.getRuntimeValues(), allOptionNames));
532533
ImageSingletons.add(TemporaryBuildDirectoryProvider.class, tempDirectoryProvider);
534+
533535
doRun(entryPoints, javaMainSupport, imageName, k, harnessSubstitutions);
534536
} finally {
535537
reporter.ensureCreationStageEndCompleted();
@@ -563,7 +565,7 @@ protected void doRun(Map<Method, CEntryPointData> entryPoints, JavaMainSupport j
563565

564566
try (DebugContext debug = new Builder(options, new GraalDebugHandlersFactory(GraalAccess.getOriginalSnippetReflection())).build();
565567
DebugCloseable featureCleanup = () -> featureHandler.forEachFeature(Feature::cleanup)) {
566-
setupNativeImage(imageName, options, entryPoints, javaMainSupport, harnessSubstitutions, debug);
568+
setupNativeImage(options, entryPoints, javaMainSupport, harnessSubstitutions, debug);
567569

568570
boolean returnAfterAnalysis = runPointsToAnalysis(imageName, options, debug);
569571
if (returnAfterAnalysis) {
@@ -758,7 +760,7 @@ protected void doRun(Map<Method, CEntryPointData> entryPoints, JavaMainSupport j
758760
try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.ARCHIVE_LAYER)) {
759761
if (ImageLayerBuildingSupport.buildingSharedLayer()) {
760762
ImageSingletonsSupportImpl.HostedManagement.persist();
761-
HostedImageLayerBuildingSupport.singleton().archiveLayer(imageName);
763+
HostedImageLayerBuildingSupport.singleton().archiveLayer();
762764
}
763765
}
764766
reporter.printCreationEnd(image.getImageFileSize(), heap.getLayerObjectCount(), image.getImageHeapSize(), codeCache.getCodeAreaSize(), numCompilations, image.getDebugInfoSize(),
@@ -917,7 +919,7 @@ protected boolean verifyAssignableTypes() {
917919
}
918920

919921
@SuppressWarnings("try")
920-
protected void setupNativeImage(String imageName, OptionValues options, Map<Method, CEntryPointData> entryPoints, JavaMainSupport javaMainSupport,
922+
protected void setupNativeImage(OptionValues options, Map<Method, CEntryPointData> entryPoints, JavaMainSupport javaMainSupport,
921923
SubstitutionProcessor harnessSubstitutions, DebugContext debug) {
922924
try (Indent ignored = debug.logAndIndent("setup native-image builder")) {
923925
try (StopTimer ignored1 = TimerCollection.createTimerAndStart(TimerCollection.Registry.SETUP)) {
@@ -996,7 +998,6 @@ protected void setupNativeImage(String imageName, OptionValues options, Map<Meth
996998
if (ImageLayerBuildingSupport.buildingSharedLayer()) {
997999
SVMImageLayerWriter imageLayerWriter = HostedConfiguration.instance().createSVMImageLayerWriter(imageLayerSnapshotUtil, useSharedLayerGraphs, useSharedLayerStrengthenedGraphs);
9981000
HostedImageLayerBuildingSupport.singleton().setWriter(imageLayerWriter);
999-
HostedImageLayerBuildingSupport.setupImageLayerArtifacts(imageName);
10001001
}
10011002

10021003
if (ImageLayerBuildingSupport.buildingExtensionLayer()) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/TemporaryBuildDirectoryProviderImpl.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,30 @@
3030
import java.nio.file.Path;
3131
import java.nio.file.SimpleFileVisitor;
3232
import java.nio.file.attribute.BasicFileAttributes;
33-
import java.util.Optional;
3433

3534
import com.oracle.svm.core.c.libc.TemporaryBuildDirectoryProvider;
3635
import com.oracle.svm.core.util.TimeUtils;
3736
import com.oracle.svm.core.util.VMError;
3837

3938
public class TemporaryBuildDirectoryProviderImpl implements TemporaryBuildDirectoryProvider, AutoCloseable {
4039

40+
private final Path tempDirectoryOptionValue;
4141
private Path tempDirectory;
4242
private boolean deleteTempDirectory;
4343

44+
public TemporaryBuildDirectoryProviderImpl(Path tempDirectoryOptionValue) {
45+
this.tempDirectoryOptionValue = tempDirectoryOptionValue;
46+
}
47+
4448
@Override
4549
public synchronized Path getTemporaryBuildDirectory() {
4650
if (tempDirectory == null) {
4751
try {
48-
Optional<Path> tempName = NativeImageOptions.TempDirectory.getValue().lastValue();
49-
if (tempName.isEmpty()) {
52+
if (tempDirectoryOptionValue == null) {
5053
tempDirectory = Files.createTempDirectory("SVM-");
5154
deleteTempDirectory = true;
5255
} else {
53-
tempDirectory = tempName.get().resolve("SVM-" + TimeUtils.currentTimeMillis());
56+
tempDirectory = tempDirectoryOptionValue.resolve("SVM-" + TimeUtils.currentTimeMillis());
5457
assert !Files.exists(tempDirectory);
5558
Files.createDirectories(tempDirectory);
5659
}

0 commit comments

Comments
 (0)