|
55 | 55 | import org.graalvm.buildtools.gradle.internal.GraalVMLogger; |
56 | 56 | import org.graalvm.buildtools.gradle.internal.GraalVMReachabilityMetadataService; |
57 | 57 | import org.graalvm.buildtools.gradle.internal.GradleUtils; |
| 58 | +import org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator; |
58 | 59 | import org.graalvm.buildtools.gradle.internal.agent.AgentConfigurationFactory; |
59 | 60 | import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; |
60 | 61 | import org.graalvm.buildtools.gradle.tasks.CollectReachabilityMetadata; |
|
65 | 66 | import org.graalvm.buildtools.gradle.tasks.UseLayerOptions; |
66 | 67 | import org.graalvm.buildtools.gradle.tasks.actions.CleanupAgentFilesAction; |
67 | 68 | import org.graalvm.buildtools.gradle.tasks.actions.MergeAgentFilesAction; |
| 69 | +import org.graalvm.buildtools.gradle.tasks.scanner.JarAnalyzerTransform; |
68 | 70 | import org.graalvm.buildtools.utils.JUnitPlatformNativeDependenciesHelper; |
69 | 71 | import org.graalvm.buildtools.utils.JUnitUtils; |
70 | | -import org.graalvm.buildtools.gradle.tasks.scanner.JarAnalyzerTransform; |
71 | 72 | import org.graalvm.buildtools.utils.SharedConstants; |
72 | 73 | import org.graalvm.reachability.DirectoryConfiguration; |
73 | 74 | import org.gradle.api.Action; |
|
100 | 101 | import org.gradle.api.plugins.JavaLibraryPlugin; |
101 | 102 | import org.gradle.api.plugins.JavaPlugin; |
102 | 103 | import org.gradle.api.plugins.JavaPluginExtension; |
103 | | -import org.gradle.api.provider.ListProperty; |
104 | 104 | import org.gradle.api.provider.MapProperty; |
105 | 105 | import org.gradle.api.provider.Property; |
106 | 106 | import org.gradle.api.provider.Provider; |
@@ -176,8 +176,15 @@ public class NativeImagePlugin implements Plugin<Project> { |
176 | 176 | private static final String REPOSITORY_COORDINATES = "org.graalvm.buildtools:graalvm-reachability-metadata:" + VersionInfo.NBT_VERSION + ":repository@zip"; |
177 | 177 | private static final String DEFAULT_URI = String.format(METADATA_REPO_URL_TEMPLATE, VersionInfo.METADATA_REPO_VERSION); |
178 | 178 |
|
| 179 | + // Compatibility Mode detection constants |
| 180 | + private static final String COMPATIBILITY_MODE_TOKEN = "-H:+CompatibilityMode"; |
| 181 | + private static final String NATIVE_IMAGE_OPTIONS_ENV = "NATIVE_IMAGE_OPTIONS"; |
| 182 | + |
179 | 183 | private GraalVMLogger logger; |
180 | 184 |
|
| 185 | + // Exposed detection provider for test binaries (to be used by follow-up tasks) |
| 186 | + private Provider<Boolean> compatModeEnabled; |
| 187 | + |
181 | 188 | @Inject |
182 | 189 | public ArchiveOperations getArchiveOperations() { |
183 | 190 | throw new UnsupportedOperationException(); |
@@ -707,6 +714,71 @@ public void registerTestBinary(Project project, |
707 | 714 | // Add DSL extension for testing |
708 | 715 | NativeImageOptions testOptions = createTestOptions(graalExtension, name, project, mainOptions, config.getSourceSet()); |
709 | 716 |
|
| 717 | + // Compute and expose the Compatibility Mode detection provider for test binary |
| 718 | + this.compatModeEnabled = computeCompatibilityModeEnabledProvider(project, testOptions); |
| 719 | + |
| 720 | + // Unified once-per-build log at configuration time if Compatibility Mode is enabled |
| 721 | + project.afterEvaluate(p -> { |
| 722 | + if (compatModeEnabled().getOrElse(false)) { |
| 723 | + logger.logOnce("Compatibility Mode detected (-H:+CompatibilityMode); The native test image will be built using the original JUnit ConsoleLauncher."); |
| 724 | + } |
| 725 | + }); |
| 726 | + |
| 727 | + // Wire main class based on Compatibility Mode, mirroring Maven plugin behavior. |
| 728 | + testOptions.getMainClass().convention(compatModeEnabled().map(c -> c |
| 729 | + ? "org.junit.platform.console.ConsoleLauncher" |
| 730 | + : "org.graalvm.junit.platform.NativeImageJUnitLauncher")); |
| 731 | + |
| 732 | + // Add the JUnit Platform Feature flag and exclude JUnit class init files only when NOT in Compatibility Mode. |
| 733 | + final String junitPlatformFeatureFlag = "--features=org.graalvm.junit.platform.JUnitPlatformFeature"; |
| 734 | + project.afterEvaluate(p -> { |
| 735 | + boolean compat = compatModeEnabled().getOrElse(false); |
| 736 | + if (!compat) { |
| 737 | + List<String> current = testOptions.getBuildArgs().getOrElse(Collections.emptyList()); |
| 738 | + if (!current.contains(junitPlatformFeatureFlag)) { |
| 739 | + testOptions.getBuildArgs().add(junitPlatformFeatureFlag); |
| 740 | + } |
| 741 | + /* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */ |
| 742 | + testOptions.getBuildArgs().addAll(JUnitUtils.excludeJUnitClassInitializationFiles()); |
| 743 | + } |
| 744 | + }); |
| 745 | + // Add XML output dir only in regular mode (not in Compatibility Mode) |
| 746 | + Provider<String> xmlOutputDir = project.getLayout().getBuildDirectory() |
| 747 | + .dir("test-results/" + name + "-native") |
| 748 | + .map(d -> d.getAsFile().getAbsolutePath()); |
| 749 | + testOptions.getRuntimeArgs().addAll( |
| 750 | + compatModeEnabled().zip(xmlOutputDir, serializableBiFunctionOf((compat, dir) -> |
| 751 | + compat ? Collections.emptyList() : Arrays.asList("--xml-output-dir", dir) |
| 752 | + )) |
| 753 | + ); |
| 754 | + // In Compatibility Mode, pass classpath and scan directive to the JUnit ConsoleLauncher to avoid |
| 755 | + // "Please specify an explicit selector option or use --scan-class-path or --scan-modules" |
| 756 | + Provider<String> cpString = project.getProviders().provider(() -> testOptions.getClasspath().getAsPath()); |
| 757 | + testOptions.getRuntimeArgs().addAll( |
| 758 | + compatModeEnabled().zip(cpString, serializableBiFunctionOf((compat, cp) -> |
| 759 | + compat ? Arrays.asList("-Djava.class.path=" + cp, "--scan-classpath") : Collections.<String>emptyList() |
| 760 | + )) |
| 761 | + ); |
| 762 | + // In Compatibility Mode, also pass -Djava.home from the GraalVM used for the build |
| 763 | + Provider<String> graalVmHome = project.getProviders().provider(() -> { |
| 764 | + NativeImageExecutableLocator.Diagnostics d = new NativeImageExecutableLocator.Diagnostics(); |
| 765 | + File nativeImage = NativeImageExecutableLocator.findNativeImageExecutable( |
| 766 | + testOptions.getJavaLauncher(), |
| 767 | + graalExtension.getToolchainDetection().map(enabled -> !enabled), |
| 768 | + graalvmHomeProvider(project.getProviders(), d), |
| 769 | + getExecOperations(), |
| 770 | + logger, |
| 771 | + d |
| 772 | + ); |
| 773 | + File parent = nativeImage.getParentFile(); |
| 774 | + return parent != null ? parent.getParent() : null; |
| 775 | + }); |
| 776 | + testOptions.getRuntimeArgs().addAll( |
| 777 | + compatModeEnabled().zip(graalVmHome, serializableBiFunctionOf((compat, home) -> |
| 778 | + compat && home != null ? Collections.singletonList("-Djava.home=" + home) : Collections.<String>emptyList() |
| 779 | + )) |
| 780 | + ); |
| 781 | + |
710 | 782 | TaskProvider<Test> testTask = config.validate().getTestTask(); |
711 | 783 | testTask.configure(test -> { |
712 | 784 | var testList = testResultsDir.dir(test.getName() + "/testlist"); |
@@ -850,23 +922,13 @@ private NativeImageOptions createTestOptions(GraalVMExtension graalExtension, |
850 | 922 | var configurations = project.getConfigurations(); |
851 | 923 | setupExtensionConfigExcludes(testExtension, configurations); |
852 | 924 |
|
853 | | - testExtension.getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); |
854 | | - testExtension.getMainClass().finalizeValue(); |
855 | 925 | testExtension.getImageName().convention(mainExtension.getImageName().map(name -> name + SharedConstants.NATIVE_TESTS_SUFFIX)); |
856 | 926 |
|
857 | | - ListProperty<String> runtimeArgs = testExtension.getRuntimeArgs(); |
858 | | - runtimeArgs.add("--xml-output-dir"); |
859 | | - runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/" + binaryName + "-native").map(d -> d.getAsFile().getAbsolutePath())); |
860 | | - |
861 | | - testExtension.buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); |
862 | 927 | ConfigurableFileCollection classpath = testExtension.getClasspath(); |
863 | 928 | classpath.from(configurations.getByName(imageClasspathConfigurationNameFor(binaryName))); |
864 | 929 | classpath.from(sourceSet.getOutput().getClassesDirs()); |
865 | 930 | classpath.from(sourceSet.getOutput().getResourcesDir()); |
866 | 931 |
|
867 | | - /* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */ |
868 | | - testExtension.getBuildArgs().addAll(JUnitUtils.excludeJUnitClassInitializationFiles()); |
869 | | - |
870 | 932 | return testExtension; |
871 | 933 | } |
872 | 934 |
|
@@ -1069,4 +1131,51 @@ public List<String> getExcludes() { |
1069 | 1131 | } |
1070 | 1132 | } |
1071 | 1133 |
|
| 1134 | + // ----------------------------- |
| 1135 | + // Compatibility Mode Detection |
| 1136 | + // ----------------------------- |
| 1137 | + |
| 1138 | + /** |
| 1139 | + * Exposes the Compatibility Mode detection for the test binary. |
| 1140 | + * Follow-up tasks will use this provider to gate configuration/skip tasks. |
| 1141 | + */ |
| 1142 | + public Provider<Boolean> compatModeEnabled() { |
| 1143 | + return compatModeEnabled; |
| 1144 | + } |
| 1145 | + |
| 1146 | + private static Provider<Boolean> computeCompatibilityModeEnabledProvider(Project project, NativeImageOptions options) { |
| 1147 | + ProviderFactory providers = project.getProviders(); |
| 1148 | + |
| 1149 | + // System environment: NATIVE_IMAGE_OPTIONS |
| 1150 | + Provider<Boolean> fromSystemEnv = providers.environmentVariable(NATIVE_IMAGE_OPTIONS_ENV) |
| 1151 | + .map(NativeImagePlugin::containsCompatibilityTokenInString) |
| 1152 | + .orElse(false); |
| 1153 | + |
| 1154 | + // Options-level environment variables |
| 1155 | + Provider<Boolean> fromOptionsEnv = options.getEnvironmentVariables() |
| 1156 | + .map(env -> { |
| 1157 | + Object v = env.get(NATIVE_IMAGE_OPTIONS_ENV); |
| 1158 | + return v != null && containsCompatibilityTokenInString(String.valueOf(v)); |
| 1159 | + }) |
| 1160 | + .orElse(false); |
| 1161 | + |
| 1162 | + // Build args on the test options |
| 1163 | + Provider<Boolean> fromBuildArgs = options.getBuildArgs() |
| 1164 | + .map(NativeImagePlugin::containsCompatibilityTokenInArgs) |
| 1165 | + .orElse(false); |
| 1166 | + |
| 1167 | + // Combine: true if any source enables compatibility mode |
| 1168 | + Provider<Boolean> anyEnv = fromSystemEnv.zip(fromOptionsEnv, (a, b) -> a || b); |
| 1169 | + return anyEnv.zip(fromBuildArgs, (ab, c) -> ab || c); |
| 1170 | + } |
| 1171 | + |
| 1172 | + private static boolean containsCompatibilityTokenInString(String value) { |
| 1173 | + return value != null && value.contains(COMPATIBILITY_MODE_TOKEN); |
| 1174 | + } |
| 1175 | + |
| 1176 | + public static boolean containsCompatibilityTokenInArgs(List<String> args) { |
| 1177 | + return args != null && args.stream() |
| 1178 | + .filter(Objects::nonNull) |
| 1179 | + .anyMatch(s -> s.equals(COMPATIBILITY_MODE_TOKEN)); |
| 1180 | + } |
1072 | 1181 | } |
0 commit comments