diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java index d9c03eb16..4b94023b1 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java @@ -60,7 +60,9 @@ public void onLoad(NativeImageConfiguration config) { "org.junit.platform.engine.UniqueIdFormat", "org.junit.platform.commons.util.ReflectionUtils", // https://github.com/graalvm/native-build-tools/issues/300 - "org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener" + "org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener", + // https://github.com/graalvm/native-build-tools/issues/602 + "org.junit.platform.commons.util.LruCache" ); if (getMajorJDKVersion() >= 21) { diff --git a/common/utils/src/main/java/org/graalvm/buildtools/agent/AgentConfiguration.java b/common/utils/src/main/java/org/graalvm/buildtools/agent/AgentConfiguration.java index 105311a43..2072b4cdb 100644 --- a/common/utils/src/main/java/org/graalvm/buildtools/agent/AgentConfiguration.java +++ b/common/utils/src/main/java/org/graalvm/buildtools/agent/AgentConfiguration.java @@ -40,13 +40,21 @@ */ package org.graalvm.buildtools.agent; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class AgentConfiguration implements Serializable { + private static final String DEFAULT_ACCESS_FILTER_FILE = "/access-filter.json"; private final Collection callerFilterFiles; private final Collection accessFilterFiles; private final Boolean builtinCallerFilter; @@ -88,6 +96,7 @@ public AgentConfiguration(Collection callerFilterFiles, } public List getAgentCommandLine() { + addDefaultAccessFilter(); List cmdLine = new ArrayList<>(agentMode.getAgentCommandLine()); appendOptionToValues("caller-filter-file=", callerFilterFiles, cmdLine); appendOptionToValues("access-filter-file=", accessFilterFiles, cmdLine); @@ -127,4 +136,34 @@ private void addToCmd(String option, Boolean value, List cmdLine) { } } + private void addDefaultAccessFilter() { + if (accessFilterFiles == null) { + // this could only happen if we instantiated disabled agent configuration + return; + } + + String tempDir = System.getProperty("java.io.tmpdir"); + Path agentDir = Path.of(tempDir).resolve("agent-config"); + Path accessFilterFile = agentDir.resolve("access-filter.json"); + if (Files.exists(accessFilterFile)) { + accessFilterFiles.add(accessFilterFile.toString()); + return; + } + + try(InputStream accessFilter = AgentConfiguration.class.getResourceAsStream(DEFAULT_ACCESS_FILTER_FILE)) { + if (accessFilter != null) { + if (!Files.exists(agentDir)) { + Files.createDirectory(agentDir); + } + + Files.copy(accessFilter, accessFilterFile, StandardCopyOption.REPLACE_EXISTING); + accessFilterFiles.add(accessFilterFile.toString()); + } else { + throw new IOException("Cannot find access-filter.json on default location: " + DEFAULT_ACCESS_FILTER_FILE); + } + } catch (IOException e) { + throw new RuntimeException("Cannot add default access-filter.json" ,e); + } + } + } diff --git a/common/utils/src/main/java/org/graalvm/buildtools/utils/ExponentialBackoff.java b/common/utils/src/main/java/org/graalvm/buildtools/utils/ExponentialBackoff.java new file mode 100644 index 000000000..bdb4dba52 --- /dev/null +++ b/common/utils/src/main/java/org/graalvm/buildtools/utils/ExponentialBackoff.java @@ -0,0 +1,128 @@ +/* + * Copyright 2003-2021 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.graalvm.buildtools.utils; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +/** + * An utility class for exponential backoff of operations which + * can fail and can be retried. + */ +public class ExponentialBackoff { + private static final int DEFAULT_MAX_RETRIES = 3; + private static final Duration DEFAULT_INITIAL_WAIT_PERIOD = Duration.of(250, ChronoUnit.MILLIS); + + private final int maxRetries; + private final Duration initialWaitPeriod; + + public ExponentialBackoff() { + this(DEFAULT_MAX_RETRIES, DEFAULT_INITIAL_WAIT_PERIOD); + } + + private ExponentialBackoff(int maxRetries, Duration initialWaitPeriod) { + if (maxRetries < 1) { + throw new IllegalArgumentException("Max retries must be at least 1"); + } + if (initialWaitPeriod.isNegative() || initialWaitPeriod.isZero()) { + throw new IllegalArgumentException("Initial backoff wait delay must be strictly positive"); + } + this.maxRetries = maxRetries; + this.initialWaitPeriod = initialWaitPeriod; + } + + public static ExponentialBackoff get() { + return new ExponentialBackoff(); + } + + /** + * The maximum number of retries. + * + * @return an exponential backoff with the specified number of retries + */ + public ExponentialBackoff withMaxRetries(int maxRetries) { + return new ExponentialBackoff(maxRetries, initialWaitPeriod); + } + + /** + * The initial backoff duration, that is to say the time we will wait + * before the first retry (there's no wait for the initial attempt). + * + * @param duration the duration for the first retry + * @return an exponential backoff with the specified initial wait period + */ + public ExponentialBackoff withInitialWaitPeriod(Duration duration) { + return new ExponentialBackoff(maxRetries, duration); + } + + /** + * Executes an operation which returns a result. Retries a maximum number of + * times by multiplying the delay between each attempt by 2. + * @param supplier the operation to execute + * @return the result of the operation + * @param the type of the result + */ + public T supply(FailableSupplier supplier) { + int attempts = maxRetries + 1; + Duration waitPeriod = initialWaitPeriod; + Exception last = null; + while (attempts > 0) { + try { + return supplier.get(); + } catch (Exception ex) { + last = ex; + attempts--; + try { + Thread.sleep(waitPeriod.toMillis()); + waitPeriod = waitPeriod.multipliedBy(2); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RetriableOperationFailedException("Thread was interrupted", e); + } + } + } + throw new RetriableOperationFailedException("Operation failed after " + maxRetries + " retries", last); + } + + /** + * Executes an operation which doesn't return any result, until it passes, + * with this exponential backoff parameters. + * See {@link #supply(FailableSupplier)} for an operation which returns a result. + * @param operation the operation to execute. + */ + public void execute(FailableOperation operation) { + supply(() -> { + operation.run(); + return null; + }); + } + + @FunctionalInterface + public interface FailableOperation { + void run() throws Exception; + } + + @FunctionalInterface + public interface FailableSupplier { + T get() throws Exception; + } + + public static final class RetriableOperationFailedException extends RuntimeException { + public RetriableOperationFailedException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/common/utils/src/main/resources/access-filter.json b/common/utils/src/main/resources/access-filter.json new file mode 100644 index 000000000..4781418b7 --- /dev/null +++ b/common/utils/src/main/resources/access-filter.json @@ -0,0 +1,13 @@ +{ + "rules": [ + { + "includeClasses": "**" + }, + { + "excludeClasses": "org.gradle.**" + }, + { + "excludeClasses": "org.junit.**" + } + ] +} \ No newline at end of file diff --git a/common/utils/src/test/java/org/graalvm/buildtools/utils/ExponentialBackoffTest.java b/common/utils/src/test/java/org/graalvm/buildtools/utils/ExponentialBackoffTest.java new file mode 100644 index 000000000..ded4212e5 --- /dev/null +++ b/common/utils/src/test/java/org/graalvm/buildtools/utils/ExponentialBackoffTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 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.graalvm.buildtools.utils; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExponentialBackoffTest { + @Test + @DisplayName("executed a passing operation") + void simpleExecution() { + AtomicBoolean success = new AtomicBoolean(); + ExponentialBackoff.get().execute(() -> success.set(true)); + assertTrue(success.get()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 3}) + @DisplayName("retries expected amount of times") + void countRetries(int retries) { + AtomicInteger count = new AtomicInteger(); + assertThrows(ExponentialBackoff.RetriableOperationFailedException.class, () -> ExponentialBackoff.get().withMaxRetries(retries) + .execute(() -> { + count.incrementAndGet(); + throw new RuntimeException(); + })); + assertEquals(retries + 1, count.get()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 3}) + @DisplayName("passes after one retry") + void passAfterRetry(int retries) { + AtomicInteger count = new AtomicInteger(); + int result = ExponentialBackoff.get().withMaxRetries(retries) + .supply(() -> { + if (count.getAndIncrement() == 0) { + throw new RuntimeException(); + } + return 200; + }); + assertEquals(2, count.get()); + assertEquals(200, result); + } + + @Test + @DisplayName("can configure initial backoff time") + void canConfigureInitialBackoffTime() { + double sd = System.currentTimeMillis(); + assertThrows(ExponentialBackoff.RetriableOperationFailedException.class, () -> ExponentialBackoff.get() + .withMaxRetries(4) + .withInitialWaitPeriod(Duration.of(1, ChronoUnit.MILLIS)) + .execute(() -> { + throw new RuntimeException(); + })); + double duration = System.currentTimeMillis() - sd; + assertTrue(duration < 100); + } + +} diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 23f732fb1..703456ede 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -19,6 +19,36 @@ If you are using alternative build systems, see <true ---- ``:: - If you want to build the image using https://blogs.oracle.com/java/post/graalvm-enterprise-221--faster-smarter-leaner[quick build mode], supply the following in the configuration of the plugin (alternatively set the `GRAALVM_QUICK_BUILD` environment variable to `true`): + If you want to build the image using https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildOutput/#qbm-use-quick-build-mode-for-faster-builds[quick build mode], supply the following in the configuration of the plugin (alternatively set the `GRAALVM_QUICK_BUILD` environment variable to `true`): [source,xml] ---- true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92a9d5957..1a7fad62a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] # Project versions -nativeBuildTools = "0.10.2" -metadataRepository = "0.3.8" +nativeBuildTools = "0.10.3" +metadataRepository = "0.3.9" # External dependencies spock = "2.1-groovy-3.0" diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy index 15e976132..7d8566b6a 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithAgentFunctionalTest.groovy @@ -42,10 +42,32 @@ package org.graalvm.buildtools.gradle import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest +import org.graalvm.buildtools.gradle.fixtures.GraalVMSupport +import org.graalvm.buildtools.utils.NativeImageUtils import spock.lang.Unroll class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { + def getCurrentJDKVersion() { + return NativeImageUtils.getMajorJDKVersion(GraalVMSupport.getGraalVMHomeVersionString()) + } + + def metadataInSingleConfigFile() { + return getCurrentJDKVersion() >= 23 + } + + def metadataExistsAt(String path) { + if (metadataInSingleConfigFile()) { + return file("${path}/reachability-metadata.json").exists() + } + + boolean allFilesExist = ['jni', 'proxy', 'reflect', 'resource', 'serialization'].every { name -> + file("${path}/${name}-config.json").exists() + } + + return allFilesExist + } + @Unroll("agent is not passed and the application fails with JUnit Platform #junitVersion") def "agent is not passed"() { given: @@ -94,18 +116,13 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { """.trim() and: - ['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name -> - assert file("build/native/agent-output/test/${name}-config.json").exists() - } + assert metadataExistsAt("build/native/agent-output/test") when: run 'metadataCopy' then: - ['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name -> - assert file("build/native/metadataCopyTest/${name}-config.json").exists() - } - + assert metadataExistsAt("build/native/metadataCopyTest") where: junitVersion = System.getProperty('versions.junit') @@ -125,7 +142,11 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { } and: - assert file("build/native/agent-output/test/reflect-config.json").text.contains("\"condition\"") + if (metadataInSingleConfigFile()) { + assert file("build/native/agent-output/test/reachability-metadata.json").text.contains("\"condition\"") + } else { + assert file("build/native/agent-output/test/reflect-config.json").text.contains("\"condition\"") + } where: junitVersion = System.getProperty('versions.junit') @@ -148,22 +169,26 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { } and: - ['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name -> - assert file("build/native/agent-output/run/${name}-config.json").exists() - } + assert metadataExistsAt("build/native/agent-output/run") when: - run'metadataCopy', '--task', 'run', '--dir', metadata_dir + run 'metadataCopy', '--task', 'run', '--dir', metadata_dir then: - ['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name -> - assert file("${metadata_dir}/${name}-config.json").exists() - } + assert metadataExistsAt(metadata_dir) and: - var reflect_config = file("${metadata_dir}/reflect-config.json") - var reflect_config_contents = reflect_config.text - assert reflect_config_contents.contains("DummyClass") && reflect_config_contents.contains("org.graalvm.demo.Message") + if (metadataInSingleConfigFile()) { + var reachabilityMetadata = file("${metadata_dir}/reachability-metadata.json") + var reachabilityMetadataContents = reachabilityMetadata.text + println reachabilityMetadataContents + assert reachabilityMetadataContents.contains("DummyClass"), reachabilityMetadataContents + assert reachabilityMetadataContents.contains("org.graalvm.demo.Message"), reachabilityMetadataContents + } else { + var reflect_config = file("${metadata_dir}/reflect-config.json") + var reflect_config_contents = reflect_config.text + assert reflect_config_contents.contains("DummyClass") && reflect_config_contents.contains("org.graalvm.demo.Message") + } when: run 'nativeRun' @@ -190,9 +215,7 @@ class JavaApplicationWithAgentFunctionalTest extends AbstractFunctionalTest { } and: - ['jni', 'proxy', 'reflect', 'resource', 'serialization'].each { name -> - assert file("build/native/agent-output/run/${name}-config.json").exists() - } + assert metadataExistsAt("build/native/agent-output/run") when: run'run', '-Pagent', '--configuration-cache', '--rerun-tasks' diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index dc4ca181f..dfa3b521a 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -64,7 +64,6 @@ import org.graalvm.buildtools.gradle.tasks.NativeRunTask; import org.graalvm.buildtools.gradle.tasks.actions.CleanupAgentFilesAction; import org.graalvm.buildtools.gradle.tasks.actions.MergeAgentFilesAction; -import org.graalvm.buildtools.gradle.tasks.actions.ProcessGeneratedGraalResourceFilesAction; import org.graalvm.buildtools.utils.SharedConstants; import org.graalvm.reachability.DirectoryConfiguration; import org.gradle.api.Action; @@ -466,6 +465,12 @@ private Provider graalVMReachabilityMetadata spec.getParameters().getUri().set(repositoryExtension.getUri().map(serializableTransformerOf(configuredUri -> computeMetadataRepositoryUri(project, repositoryExtension, m -> logFallbackToDefaultUri(m, logger))))); spec.getParameters().getCacheDir().set( new File(project.getGradle().getGradleUserHomeDir(), "native-build-tools/repositories")); + spec.getParameters().getBackoffMaxRetries().convention( + GradleUtils.intProperty(project.getProviders(), "exponential.backoff.max.retries", 3) + ); + spec.getParameters().getInitialBackoffMillis().convention( + GradleUtils.intProperty(project.getProviders(), "exponential.backoff.initial.delay", 100) + ); }); } @@ -865,11 +870,6 @@ public void execute(@Nonnull Task task) { execOperations)); taskToInstrument.doLast(new CleanupAgentFilesAction(mergeInputDirs, fileOperations)); - - taskToInstrument.doLast(new ProcessGeneratedGraalResourceFilesAction( - outputDir, - graalExtension.getAgent().getFilterableEntries() - )); } private static void injectTestPluginDependencies(Project project, Property testSupportEnabled) { diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/DefaultGraalVmExtension.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/DefaultGraalVmExtension.java index 1d4235f6c..9bc4d1662 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/DefaultGraalVmExtension.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/DefaultGraalVmExtension.java @@ -55,7 +55,6 @@ import org.gradle.jvm.toolchain.JavaToolchainService; import javax.inject.Inject; -import java.util.Arrays; public abstract class DefaultGraalVmExtension implements GraalVMExtension { private final transient NamedDomainObjectContainer nativeImages; @@ -79,7 +78,6 @@ public DefaultGraalVmExtension(NamedDomainObjectContainer na agentOpts.getEnabled().convention(false); agentOpts.getModes().getConditional().getParallel().convention(true); agentOpts.getMetadataCopy().getMergeWithExisting().convention(false); - agentOpts.getFilterableEntries().convention(Arrays.asList("org.gradle.", "org.junit.")); agentOpts.getBuiltinHeuristicFilter().convention(true); agentOpts.getBuiltinCallerFilter().convention(true); agentOpts.getEnableExperimentalPredefinedClasses().convention(false); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java index e648712b8..fd0537ef2 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java @@ -40,6 +40,7 @@ */ package org.graalvm.buildtools.gradle.internal; +import org.graalvm.buildtools.utils.ExponentialBackoff; import org.graalvm.buildtools.utils.FileUtils; import org.graalvm.reachability.DirectoryConfiguration; import org.graalvm.reachability.GraalVMReachabilityMetadataRepository; @@ -66,6 +67,7 @@ import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.Collection; import java.util.Map; import java.util.Objects; @@ -85,6 +87,10 @@ public abstract class GraalVMReachabilityMetadataService implements BuildService protected abstract FileSystemOperations getFileOperations(); public interface Params extends BuildServiceParameters { + Property getBackoffMaxRetries(); + + Property getInitialBackoffMillis(); + Property getLogLevel(); Property getUri(); @@ -124,14 +130,16 @@ private GraalVMReachabilityMetadataRepository newRepository(URI uri) throws URIS throw new RuntimeException(e); } } - - try (ReadableByteChannel readableByteChannel = Channels.newChannel(uri.toURL().openStream())) { - try (FileOutputStream fileOutputStream = new FileOutputStream(zipped)) { - fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); - } - } catch (IOException e) { - throw new RuntimeException(e); - } + ExponentialBackoff.get() + .withMaxRetries(getParameters().getBackoffMaxRetries().get()) + .withInitialWaitPeriod(Duration.ofMillis(getParameters().getInitialBackoffMillis().get())) + .execute(() -> { + try (ReadableByteChannel readableByteChannel = Channels.newChannel(uri.toURL().openStream())) { + try (FileOutputStream fileOutputStream = new FileOutputStream(zipped)) { + fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } + }); } return newRepositoryFromZipFile(cacheKey, zipped, logLevel); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java index 6d998da56..103a5f13c 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GradleUtils.java @@ -48,6 +48,8 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.util.GradleVersion; @@ -77,17 +79,28 @@ public static FileCollection transitiveProjectArtifacts(Project project, String ConfigurableFileCollection transitiveProjectArtifacts = project.getObjects().fileCollection(); transitiveProjectArtifacts.from(findMainArtifacts(project)); transitiveProjectArtifacts.from(findConfiguration(project, name) - .getIncoming() - .artifactView(view -> view.componentFilter(ProjectComponentIdentifier.class::isInstance)) - .getFiles()); + .getIncoming() + .artifactView(view -> view.componentFilter(ProjectComponentIdentifier.class::isInstance)) + .getFiles()); return transitiveProjectArtifacts; } public static FileCollection findMainArtifacts(Project project) { return findConfiguration(project, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME) - .getOutgoing() - .getArtifacts() - .getFiles(); + .getOutgoing() + .getArtifacts() + .getFiles(); } + public static Provider intProperty(ProviderFactory providers, String propertyName, int defaultValue) { + return stringProperty(providers, propertyName) + .map(Integer::parseInt) + .orElse(defaultValue); + } + + private static Provider stringProperty(ProviderFactory providers, String propertyName) { + return providers.systemProperty(propertyName) + .orElse(providers.gradleProperty(propertyName)) + .orElse(providers.environmentVariable(propertyName.replace('.', '_').toUpperCase())); + } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageExecutableLocator.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageExecutableLocator.java index ae05fcb61..0a77f440d 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageExecutableLocator.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageExecutableLocator.java @@ -91,29 +91,29 @@ public static File findNativeImageExecutable(Property javaLauncher executablePath = metadata.getInstallationPath().file("bin/" + NATIVE_IMAGE_EXE).getAsFile(); } - try { - if (!executablePath.exists()) { - logger.log("Native Image executable wasn't found. We will now try to download it. "); - File graalVmHomeGuess = executablePath.getParentFile(); - - File guPath = graalVmHomeGuess.toPath().resolve(GU_EXE).toFile(); - if (!guPath.exists()) { - throw new GradleException("'" + GU_EXE + "' at '" + guPath + "' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution."); - } - ExecResult res = execOperations.exec(spec -> { - spec.args("install", "native-image"); - spec.setExecutable(Paths.get(graalVmHomeGuess.getAbsolutePath(), GU_EXE)); - }); - if (res.getExitValue() != 0) { - throw new GradleException("Native Image executable wasn't found, and '" + GU_EXE + "' tool failed to install it."); - } - diagnostics.withGuInstall(); + File graalVmHomeGuess = executablePath.getParentFile(); + File guPath = graalVmHomeGuess.toPath().resolve(GU_EXE).toFile(); + if (guPath.exists() && !executablePath.exists()) { + logger.log("Native Image executable wasn't found. We will now try to download it. "); + + ExecResult res = execOperations.exec(spec -> { + spec.args("install", "native-image"); + spec.setExecutable(Paths.get(graalVmHomeGuess.getAbsolutePath(), GU_EXE)); + }); + if (res.getExitValue() != 0) { + throw new GradleException("Native Image executable wasn't found, and '" + GU_EXE + "' tool failed to install it.\n" + + "Make sure to declare the GRAALVM_HOME or JAVA_HOME environment variable or install GraalVM with " + + "native-image in a standard location recognized by Gradle Java toolchain support"); } - } catch (GradleException e) { - throw new GradleException("Determining GraalVM installation failed with message: " + e.getMessage() + "\n\n" - + "Make sure to declare the GRAALVM_HOME environment variable or install GraalVM with " + - "native-image in a standard location recognized by Gradle Java toolchain support"); + diagnostics.withGuInstall(); } + + if (!executablePath.exists()) { + throw new GradleException(executablePath + " wasn't found. This probably means that JDK isn't a GraalVM distribution.\n" + + "Make sure to declare the GRAALVM_HOME or JAVA_HOME environment variable or install GraalVM with" + + "native-image in a standard location recognized by Gradle Java toolchain support"); + } + diagnostics.withExecutablePath(executablePath); return executablePath; } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/MergeAgentFilesAction.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/MergeAgentFilesAction.java index 013479ce4..e2040b9b2 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/MergeAgentFilesAction.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/MergeAgentFilesAction.java @@ -58,6 +58,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.function.Supplier; import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.findNativeImageExecutable; @@ -94,8 +95,11 @@ public MergeAgentFilesAction(Provider isMergingEnabled, this.noLauncherProperty = objectFactory.property(JavaLauncher.class); } + private static final Set METADATA_FILES = Set.of("reflect-config.json", "jni-config.json", "proxy-config.json", "resource-config.json", "reachability-metadata.json"); + private static boolean isConfigDir(String dir) { - return Arrays.stream(new File(dir).listFiles()).anyMatch(file -> file.getName().equals("reflect-config.json")); + return Arrays.stream(new File(dir).listFiles()) + .anyMatch(file -> METADATA_FILES.contains(file.getName())); } @Override diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/ProcessGeneratedGraalResourceFilesAction.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/ProcessGeneratedGraalResourceFilesAction.java deleted file mode 100644 index aaefbd3a0..000000000 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/actions/ProcessGeneratedGraalResourceFilesAction.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * The Universal Permissive License (UPL), Version 1.0 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.graalvm.buildtools.gradle.tasks.actions; - -import groovy.json.JsonGenerator; -import groovy.json.JsonOutput; -import groovy.json.JsonSlurper; -import org.gradle.api.Action; -import org.gradle.api.GradleException; -import org.gradle.api.Task; -import org.gradle.api.file.Directory; -import org.gradle.api.provider.ListProperty; -import org.gradle.api.provider.Provider; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * This task is responsible for processing the JSON files generated by - * the GraalVM agent, in particular to filter out entries which are - * inherited from the Gradle environment itself. - */ -public class ProcessGeneratedGraalResourceFilesAction implements Action { - private final Provider inputDirectory; - private final ListProperty filterableEntries; - - public ProcessGeneratedGraalResourceFilesAction(Provider inputDirectory, ListProperty filterableEntries) { - this.inputDirectory = inputDirectory; - this.filterableEntries = filterableEntries; - } - - @Override - public void execute(Task task) { - try { - for (File resourceFile : inputDirectory.get().getAsFileTree()) { - processFile(resourceFile); - } - } catch (IOException e) { - throw new GradleException("An IO error occured when processing the agent generated files", e); - } - } - - protected void processFile(File file) throws IOException { - if (file.getName().endsWith(".json")) { - processJsonFile(file); - } - } - - protected void processJsonFile(File jsonFile) throws IOException { - JsonSlurper json = new JsonSlurper(); - Object result = json.parse(jsonFile); - Object filtered = filter(result); - JsonGenerator generator = new JsonGenerator.Options() - .build(); - String processed = JsonOutput.prettyPrint(generator.toJson(filtered)); - try (Writer writer = new OutputStreamWriter(new FileOutputStream(jsonFile), StandardCharsets.UTF_8)) { - writer.write(processed); - } - } - - /** - * Filters the parsed JSON file to remove entries which are configured - * by the filterable entries parameter. This is a very rough algorithm - * which would deserve specific implementation for each JSON format. - * Instead it takes a "brute force" approach which may result in some - * weird errors. - */ - @SuppressWarnings("unchecked") - private Object filter(Object in) { - Class clazz = in.getClass(); - if (shouldFilterString(in)) { - return null; - } - if (List.class.isAssignableFrom(clazz)) { - return filterList((List) in); - } - if (Map.class.isAssignableFrom(clazz)) { - return filterMap((Map) in); - } - return in; - } - - private Map filterMap(Map map) { - if (shouldFilterString(map.get("name"))) { - return null; - } - Map out = new HashMap<>(map.size()); - for (Map.Entry entry : map.entrySet()) { - Object key = entry.getKey(); - Object value = entry.getValue(); - if (shouldFilterString(value)) { - continue; - } - out.put(key, filter(value)); - } - return out; - } - - private boolean shouldFilterString(Object value) { - if (value instanceof CharSequence) { - String string = value.toString(); - return filterableEntries.get().stream().anyMatch(string::startsWith); - } - return false; - } - - private List filterList(List in) { - List out = new ArrayList<>(in.size()); - for (Object element : in) { - Object filtered = filter(element); - if (filtered == null || (filtered instanceof Collection && ((Collection) filtered).isEmpty())) { - continue; - } - out.add(filtered); - } - return out; - } -} diff --git a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy index 9c357ebd6..344bccee4 100644 --- a/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy +++ b/native-gradle-plugin/src/testFixtures/groovy/org/graalvm/buildtools/gradle/fixtures/GraalVMSupport.groovy @@ -53,6 +53,27 @@ class GraalVMSupport { (System.getProperty("java.vendor.version", "") - 'GraalVM' - 'CE' - 'EE').trim() } + static String getJavaHomeVersionString() { + String javaHomeLocation = System.getenv("JAVA_HOME") + return extractVersionString(javaHomeLocation) + } + + static String getGraalVMHomeVersionString() { + String graalvmHomeLocation = System.getenv("GRAALVM_HOME") + return extractVersionString(graalvmHomeLocation) + } + + private static String extractVersionString(String location) { + def sout = new StringBuilder(), serr = new StringBuilder() + String command = getSystemBasedCommand(location); + def proc = command.execute() + proc.consumeProcessOutput(sout, serr) + proc.waitForOrKill(1000) + assert serr.toString().isEmpty() + + return sout.toString() + } + static int getMajorVersion() { String v = version v.substring(0, v.indexOf('.')).toInteger() @@ -62,4 +83,12 @@ class GraalVMSupport { String v = version - "${majorVersion}." v.substring(0, v.indexOf('.')).toInteger() } + + private static String getSystemBasedCommand(String location) { + if (System.getProperty("os.name", "unknown").contains("Windows")) { + return location + '\\bin\\native-image.cmd --version' + } else { + return location + '/bin/native-image --version' + } + } } diff --git a/native-maven-plugin/reproducers/issue-144/pom.xml b/native-maven-plugin/reproducers/issue-144/pom.xml index 0f07a3185..c998b5f0c 100644 --- a/native-maven-plugin/reproducers/issue-144/pom.xml +++ b/native-maven-plugin/reproducers/issue-144/pom.xml @@ -56,8 +56,8 @@ 1.8 UTF-8 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 example-app org.graalvm.demo.Application diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy index 2b8aa6280..88b943bf2 100644 --- a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy @@ -131,4 +131,16 @@ class JavaApplicationFunctionalTest extends AbstractGraalVMMavenFunctionalTest { file("target/").listFiles().findAll(x->x.name.contains("native-image") && x.name.endsWith(".args")).size() == 1 } + def "can handle spaces when writing the args file"() { + withSpacesInProjectDir() + withSample("java-application") + + when: + mvn '-DquickBuild', '-Pnative', 'native:write-args-file' + + then: + buildSucceeded + outputContains "Args file written to: target" + File.separator + "native-image" + } + } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java index fa5e563bd..35a09382b 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java @@ -242,9 +242,7 @@ protected List getBuildArgs() throws MojoExecutionException { } if (buildArgs != null && !buildArgs.isEmpty()) { - for (String buildArg : buildArgs) { - cliArgs.addAll(Arrays.asList(buildArg.split("\\s+"))); - } + cliArgs.addAll(processBuildArgs(buildArgs)); } List actualCliArgs; @@ -262,6 +260,18 @@ protected List getBuildArgs() throws MojoExecutionException { return Collections.unmodifiableList(actualCliArgs); } + static List processBuildArgs(List buildArgs) { + var result = new ArrayList(); + for (String buildArg : buildArgs) { + if(buildArg.startsWith("\\Q") || buildArg.startsWith("-H:ConfigurationFileDirectories")) { + result.add(buildArg); + } else { + result.addAll(Arrays.asList(buildArg.split("\\s+", 2))); + } + } + return result; + } + protected Path processSupportedArtifacts(Artifact artifact) throws MojoExecutionException { return processArtifact(artifact, "jar", "test-jar", "war"); } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java index bfe70a5d5..6ceb4736c 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java @@ -59,6 +59,7 @@ import org.eclipse.aether.resolution.DependencyResult; import org.graalvm.buildtools.VersionInfo; import org.graalvm.buildtools.maven.config.MetadataRepositoryConfiguration; +import org.graalvm.buildtools.utils.ExponentialBackoff; import org.graalvm.buildtools.utils.FileUtils; import org.graalvm.reachability.DirectoryConfiguration; import org.graalvm.reachability.GraalVMReachabilityMetadataRepository; @@ -73,6 +74,7 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -104,6 +106,12 @@ public abstract class AbstractNativeMojo extends AbstractMojo { @Parameter(alias = "metadataRepository") protected MetadataRepositoryConfiguration metadataRepositoryConfiguration; + @Parameter(defaultValue = "3") + protected int metadataRepositoryMaxRetries; + + @Parameter(defaultValue = "100") + protected int metadataRepositoryInitialBackoffMillis; + protected final Set metadataRepositoryConfigurations; protected GraalVMReachabilityMetadataRepository metadataRepository; @@ -207,7 +215,11 @@ private Path getRepo(Path destinationRoot) { } } - return downloadMetadataRepo(destinationRoot, targetUrl); + URL finalTargetUrl = targetUrl; + return ExponentialBackoff.get() + .withMaxRetries(metadataRepositoryMaxRetries) + .withInitialWaitPeriod(Duration.ofMillis(metadataRepositoryInitialBackoffMillis)) + .supply(() -> downloadMetadataRepo(destinationRoot, finalTargetUrl)); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java index d5b5a19f4..f752db165 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java @@ -48,11 +48,9 @@ import org.codehaus.plexus.util.FileUtils; import org.graalvm.buildtools.maven.config.AbstractMergeAgentFilesMojo; import org.graalvm.buildtools.maven.config.agent.AgentConfiguration; -import org.graalvm.buildtools.utils.NativeImageConfigurationUtils; import java.io.File; import java.io.IOException; -import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -116,14 +114,12 @@ public void execute() throws MojoExecutionException { private void mergeForGivenDir(String agentOutputDirectory) throws MojoExecutionException { File baseDir = new File(agentOutputDirectory); if (baseDir.exists()) { - Path nativeImageExecutable = NativeImageConfigurationUtils.getNativeImage(logger); - tryInstallMergeExecutable(nativeImageExecutable); List sessionDirectories = sessionDirectoriesFrom(baseDir.listFiles()).collect(Collectors.toList()); if (sessionDirectories.size() == 0) { sessionDirectories = Collections.singletonList(baseDir); } - invokeMerge(mergerExecutable, sessionDirectories, baseDir); + invokeMerge(sessionDirectories, baseDir); } else { getLog().debug("Agent output directory " + baseDir + " doesn't exist. Skipping merge."); } @@ -135,11 +131,8 @@ private static Stream sessionDirectoriesFrom(File[] files) { .filter(f -> f.getName().startsWith("session-")); } - private void invokeMerge(File mergerExecutable, List inputDirectories, File outputDirectory) throws MojoExecutionException { - if (!mergerExecutable.exists()) { - getLog().warn("Cannot merge agent files because native-image-configure is not installed. Please upgrade to a newer version of GraalVM."); - return; - } + private void invokeMerge(List inputDirectories, File outputDirectory) throws MojoExecutionException { + File mergerExecutable = getMergerExecutable(); try { if (inputDirectories.isEmpty()) { getLog().warn("Skipping merging of agent files since there are no input directories."); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MetadataCopyMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MetadataCopyMojo.java index 4797fecda..67b7b5494 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MetadataCopyMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MetadataCopyMojo.java @@ -50,12 +50,10 @@ import org.graalvm.buildtools.maven.config.AbstractMergeAgentFilesMojo; import org.graalvm.buildtools.maven.config.agent.AgentConfiguration; import org.graalvm.buildtools.maven.config.agent.MetadataCopyConfiguration; -import org.graalvm.buildtools.utils.NativeImageConfigurationUtils; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; @@ -68,7 +66,8 @@ public class MetadataCopyMojo extends AbstractMergeAgentFilesMojo { private static final String DEFAULT_OUTPUT_DIRECTORY = "/META-INF/native-image"; - private static final List FILES_REQUIRED_FOR_MERGE = Arrays.asList("reflect-config.json", "jni-config.json", "proxy-config.json", "resource-config.json"); + private static final List FILES_REQUIRED_FOR_MERGE_LEGACY = Arrays.asList("reflect-config.json", "jni-config.json", "proxy-config.json", "resource-config.json"); + private static final List FILES_REQUIRED_FOR_MERGE = Arrays.asList("reachability-metadata.json"); @Parameter(alias = "agent") private AgentConfiguration agentConfiguration; @@ -113,8 +112,6 @@ public void execute() throws MojoExecutionException { } } - Path nativeImageExecutable = NativeImageConfigurationUtils.getNativeImage(logger); - tryInstallMergeExecutable(nativeImageExecutable); executeCopy(buildDirectory, destinationDir); getLog().info("Metadata copy process finished."); } @@ -140,7 +137,7 @@ private void executeCopy(String buildDirectory, String destinationDir) throws Mo throw new MojoExecutionException("There are missing files for merge in output directory. If you want to merge agent files with " + "existing files in output directory, please make sure that output directory contains all of the following files: " + - "reflect-config.json, jni-config.json, proxy-config.json, resource-config.json. Currently the output directory is " + + "reflect-config.json, jni-config.json, proxy-config.json, resource-config.json, reachability-metadata.json. Currently the output directory is " + "missing: " + missingFiles); } @@ -155,7 +152,7 @@ private void executeCopy(String buildDirectory, String destinationDir) throws Mo logger.info("Copying files from: " + sourceDirsInfo); List nativeImageConfigureOptions = new StandardAgentMode().getNativeImageConfigureOptions(sourceDirectories, Collections.singletonList(destinationDir)); - nativeImageConfigureOptions.add(0, mergerExecutable.getAbsolutePath()); + nativeImageConfigureOptions.add(0, getMergerExecutable().getAbsolutePath()); ProcessBuilder processBuilder = new ProcessBuilder(nativeImageConfigureOptions); try { @@ -213,7 +210,8 @@ private boolean dirContainsFilesForMerge(String dir) { } List dirContent = Arrays.stream(content).map(File::getName).collect(Collectors.toList()); - return getListDiff(FILES_REQUIRED_FOR_MERGE, dirContent).isEmpty(); + return getListDiff(FILES_REQUIRED_FOR_MERGE, dirContent).isEmpty() || + getListDiff(FILES_REQUIRED_FOR_MERGE_LEGACY, dirContent).isEmpty(); } private List getListDiff(List list1, List list2) { diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/AbstractMergeAgentFilesMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/AbstractMergeAgentFilesMojo.java index 0e5b8b440..3323845ea 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/AbstractMergeAgentFilesMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/AbstractMergeAgentFilesMojo.java @@ -42,12 +42,12 @@ package org.graalvm.buildtools.maven.config; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.codehaus.plexus.logging.Logger; -import org.graalvm.buildtools.utils.NativeImageUtils; +import org.graalvm.buildtools.utils.NativeImageConfigurationUtils; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import static org.graalvm.buildtools.utils.NativeImageUtils.nativeImageConfigureFileName; @@ -58,32 +58,26 @@ public abstract class AbstractMergeAgentFilesMojo extends AbstractMojo { @Component protected Logger logger; - protected File mergerExecutable; + private File mergerExecutable; - protected void tryInstallMergeExecutable(Path nativeImageExecutablePath) { - if (mergerExecutable != null && mergerExecutable.exists()) { - return; + public File getMergerExecutable() throws MojoExecutionException { + if (mergerExecutable == null) { + initializeMergerExecutable(); } - File nativeImageExecutable = nativeImageExecutablePath.toAbsolutePath().toFile(); - File mergerExecutable = new File(nativeImageExecutable.getParentFile(), nativeImageConfigureFileName()); - if (!mergerExecutable.exists()) { - getLog().info("Installing native image merger to " + mergerExecutable); - ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString()); - processBuilder.command().add("--macro:native-image-configure-launcher"); - processBuilder.directory(mergerExecutable.getParentFile()); - processBuilder.inheritIO(); - - try { - Process installProcess = processBuilder.start(); - if (installProcess.waitFor() != 0) { - getLog().warn("Installation of native image merging tool failed"); - } - NativeImageUtils.maybeCreateConfigureUtilSymlink(mergerExecutable, nativeImageExecutablePath); - } catch (IOException | InterruptedException e) { - // ignore since we will handle that if the installer doesn't exist later - } + return mergerExecutable; + } + private void initializeMergerExecutable() throws MojoExecutionException { + Path nativeImage = NativeImageConfigurationUtils.getNativeImage(logger); + File nativeImageExecutable = nativeImage.toAbsolutePath().toFile(); + String nativeImageConfigureFileName = nativeImageConfigureFileName(); + File mergerExecutable = new File(nativeImageExecutable.getParentFile(), nativeImageConfigureFileName); + if (!mergerExecutable.exists()) { + throw new MojoExecutionException("The '" + nativeImageConfigureFileName + "' tool was not found in the GraalVM JDK at '" + nativeImageExecutable.getParentFile().getParentFile() + "'." + + "This probably means that you are using a GraalVM distribution that is not fully supported by the Native Build Tools. " + + "Please try again, for example, with Oracle GraalVM or GraalVM Community Edition." + ); } this.mergerExecutable = mergerExecutable; diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java index 2dadfedc3..364e051c6 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/utils/NativeImageConfigurationUtils.java @@ -69,34 +69,31 @@ public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failF Path graalHomePath = Paths.get(graalHome); Path nativeImageExe = graalHomePath.resolve("bin").resolve(NATIVE_IMAGE_EXE); + Path guExe = graalHomePath.resolve("bin").resolve(GU_EXE); - if (!Files.exists(nativeImageExe)) { - Path guExe = graalHomePath.resolve("bin").resolve(GU_EXE); - if (Files.exists(guExe)) { - ProcessBuilder processBuilder = new ProcessBuilder(guExe.toString(), "install", "native-image"); - processBuilder.inheritIO(); - try { - Process nativeImageFetchingProcess = processBuilder.start(); - if (nativeImageFetchingProcess.waitFor() != 0) { - throw new MojoExecutionException("native-image was not found, and '" + GU_EXE + "' tool failed to install it."); - } - } catch (MojoExecutionException | IOException | InterruptedException e) { - throw new MojoExecutionException("Determining GraalVM installation failed with message: " + e.getMessage()); + if (Files.exists(guExe) && !Files.exists(nativeImageExe)) { + ProcessBuilder processBuilder = new ProcessBuilder(guExe.toString(), "install", "native-image"); + processBuilder.inheritIO(); + try { + Process nativeImageFetchingProcess = processBuilder.start(); + if (nativeImageFetchingProcess.waitFor() != 0) { + throw new MojoExecutionException("native-image was not found, and '" + GU_EXE + "' tool failed to install it."); } - } else if (failFast) { - throw new MojoExecutionException("'" + GU_EXE + "' tool was not found in your " + javaHomeVariable + "." + - "This probably means that the JDK at '" + graalHomePath + "' is not a GraalVM distribution."); + } catch (MojoExecutionException | IOException | InterruptedException e) { + throw new MojoExecutionException("Determining GraalVM installation failed with message: " + e.getMessage()); } } if (!Files.exists(nativeImageExe)) { if (failFast) { - throw new RuntimeException("native-image is not installed in your " + javaHomeVariable + "." + - "This probably means that the JDK at '" + graalHomePath + "' is not a GraalVM distribution."); + throw new MojoExecutionException("native-image is not installed in your " + javaHomeVariable + "." + + "This probably means that the JDK at '" + graalHomePath + "' is not a GraalVM distribution. " + + "The GraalVM Native Maven Plugin requires GRAALVM_HOME or JAVA_HOME to be a GraalVM distribution."); } else { return null; } } + logger.info("Found GraalVM installation from " + javaHomeVariable + " variable."); return nativeImageExe; } @@ -128,8 +125,8 @@ public static Path getNativeImage(Logger logger) throws MojoExecutionException { } if (nativeImage == null) { - throw new RuntimeException("GraalVM native-image is missing on your system. " + System.lineSeparator() + - "Make sure that GRAALVM_HOME environment variable is present."); + throw new RuntimeException("The 'native-image' tool was not found on your system. " + + "Make sure that the JAVA_HOME or GRAALVM_HOME environment variables point to a GraalVM JDK, or that 'native-image' is on the system path."); } nativeImageExeCache = nativeImage; diff --git a/native-maven-plugin/src/test/groovy/org/graalvm/buildtools/maven/AbstractNativeImageMojoTest.groovy b/native-maven-plugin/src/test/groovy/org/graalvm/buildtools/maven/AbstractNativeImageMojoTest.groovy new file mode 100644 index 000000000..dc2df79ce --- /dev/null +++ b/native-maven-plugin/src/test/groovy/org/graalvm/buildtools/maven/AbstractNativeImageMojoTest.groovy @@ -0,0 +1,30 @@ +package org.graalvm.buildtools.maven + +import spock.lang.Specification + +class AbstractNativeImageMojoTest extends Specification { + + void "it can process build args"() { + given: + def buildArgs = [ + "--exclude-config", + "\\QC:\\Users\\Lahoucine EL ADDALI\\.m2\\repository\\io\\netty\\netty-transport\\4.1.108.Final\\netty-transport-4.1.108.Final.jar\\E", + "^/META-INF/native-image/", + "-cp C:\\Users\\Lahoucine EL ADDALI\\Desktop\\outdir\\target/java-application-with-custom-packaging-0.1.jar", + "-H:ConfigurationFileDirectories=C:\\Users\\Lahoucine EL ADDALI\\Downloads\\4.5.0.0_kubernetes_kubernetes-demo-java-maven\\api\\target\\native\\generated\\generateResourceConfig" + ] + + when: + def processedArgs = AbstractNativeImageMojo.processBuildArgs(buildArgs) + + then: + processedArgs == [ + "--exclude-config", + "\\QC:\\Users\\Lahoucine EL ADDALI\\.m2\\repository\\io\\netty\\netty-transport\\4.1.108.Final\\netty-transport-4.1.108.Final.jar\\E", + "^/META-INF/native-image/", + "-cp", + "C:\\Users\\Lahoucine EL ADDALI\\Desktop\\outdir\\target/java-application-with-custom-packaging-0.1.jar", + "-H:ConfigurationFileDirectories=C:\\Users\\Lahoucine EL ADDALI\\Downloads\\4.5.0.0_kubernetes_kubernetes-demo-java-maven\\api\\target\\native\\generated\\generateResourceConfig" + ] + } +} diff --git a/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy b/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy index 00c593bc6..a02de1d93 100644 --- a/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy +++ b/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy @@ -43,7 +43,6 @@ package org.graalvm.buildtools.maven import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector -import org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker import org.eclipse.jetty.server.handler.ContextHandler import org.eclipse.jetty.server.handler.ResourceHandler import spock.lang.Specification @@ -57,7 +56,7 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { @TempDir Path testDirectory - Path testOrigin; + Path testOrigin private IsolatedMavenExecutor executor @@ -66,9 +65,9 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { Server server ServerConnector connector - boolean IS_WINDOWS = System.getProperty("os.name", "unknown").contains("Windows"); - boolean IS_LINUX = System.getProperty("os.name", "unknown").contains("Linux"); - boolean IS_MAC = System.getProperty("os.name", "unknown").contains("Mac"); + boolean IS_WINDOWS = System.getProperty("os.name", "unknown").contains("Windows") + boolean IS_LINUX = System.getProperty("os.name", "unknown").contains("Linux") + boolean IS_MAC = System.getProperty("os.name", "unknown").contains("Mac") def setup() { executor = new IsolatedMavenExecutor( @@ -154,12 +153,13 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { } void mvn(List args, Map systemProperties) { - System.out.println("Running copy of maven project `" + testOrigin + "` with " + args); + println("Running copy of maven project ${testOrigin} in ${testDirectory} with $args") var resultingSystemProperties = [ "common.repo.uri": System.getProperty("common.repo.uri"), "seed.repo.uri": System.getProperty("seed.repo.uri"), "maven.repo.local": testDirectory.resolve("local-repo").toFile().absolutePath ] + println "Using local repo: ${resultingSystemProperties['maven.repo.local']}" resultingSystemProperties.putAll(systemProperties) result = executor.execute( @@ -169,7 +169,7 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { *args], new File(System.getProperty("maven.settings")) ) - System.out.println("Exit code is ${result.exitCode}") + println "Exit code is ${result.exitCode}" } diff --git a/samples/java-application-with-custom-packaging/pom.xml b/samples/java-application-with-custom-packaging/pom.xml index 80cf8b6a3..39a790bb7 100644 --- a/samples/java-application-with-custom-packaging/pom.xml +++ b/samples/java-application-with-custom-packaging/pom.xml @@ -61,7 +61,7 @@ 3.3.4 org.graalvm.demo.Application netty - 0.10.2 + 0.10.3 diff --git a/samples/java-application-with-custom-tests/gradle.properties b/samples/java-application-with-custom-tests/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-application-with-custom-tests/gradle.properties +++ b/samples/java-application-with-custom-tests/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-application-with-extra-sourceset/gradle.properties b/samples/java-application-with-extra-sourceset/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-application-with-extra-sourceset/gradle.properties +++ b/samples/java-application-with-extra-sourceset/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-application-with-reflection/gradle.properties b/samples/java-application-with-reflection/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-application-with-reflection/gradle.properties +++ b/samples/java-application-with-reflection/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-application-with-reflection/pom.xml b/samples/java-application-with-reflection/pom.xml index 10dd0a3ab..a0c675aeb 100644 --- a/samples/java-application-with-reflection/pom.xml +++ b/samples/java-application-with-reflection/pom.xml @@ -52,8 +52,8 @@ 1.8 UTF-8 5.10.0 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 example-app org.graalvm.demo.Application diff --git a/samples/java-application-with-resources/gradle.properties b/samples/java-application-with-resources/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-application-with-resources/gradle.properties +++ b/samples/java-application-with-resources/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-application-with-resources/pom.xml b/samples/java-application-with-resources/pom.xml index dbfe6d0f7..cb11f0a2d 100644 --- a/samples/java-application-with-resources/pom.xml +++ b/samples/java-application-with-resources/pom.xml @@ -51,9 +51,9 @@ 1.8 UTF-8 - 0.10.2 + 0.10.3 5.10.0 - 0.10.2 + 0.10.3 example-app org.graalvm.demo.Application diff --git a/samples/java-application-with-tests/gradle.properties b/samples/java-application-with-tests/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-application-with-tests/gradle.properties +++ b/samples/java-application-with-tests/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-application-with-tests/pom.xml b/samples/java-application-with-tests/pom.xml index b30e970db..e276f1ad8 100644 --- a/samples/java-application-with-tests/pom.xml +++ b/samples/java-application-with-tests/pom.xml @@ -52,8 +52,8 @@ 1.8 UTF-8 5.10.0 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 example-app org.graalvm.demo.Application diff --git a/samples/java-application/gradle.properties b/samples/java-application/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-application/gradle.properties +++ b/samples/java-application/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-application/pom.xml b/samples/java-application/pom.xml index 0fbb2bc5a..062faf43b 100644 --- a/samples/java-application/pom.xml +++ b/samples/java-application/pom.xml @@ -51,8 +51,8 @@ 1.8 UTF-8 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 example-app org.graalvm.demo.Application diff --git a/samples/java-library/gradle.properties b/samples/java-library/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/java-library/gradle.properties +++ b/samples/java-library/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/java-library/pom.xml b/samples/java-library/pom.xml index 52659ff60..adf9dc1cd 100644 --- a/samples/java-library/pom.xml +++ b/samples/java-library/pom.xml @@ -51,8 +51,8 @@ 1.8 UTF-8 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 java-library diff --git a/samples/kotlin-application-with-tests/gradle.properties b/samples/kotlin-application-with-tests/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/kotlin-application-with-tests/gradle.properties +++ b/samples/kotlin-application-with-tests/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/metadata-repo-integration/gradle.properties b/samples/metadata-repo-integration/gradle.properties index 76fe24d37..5166f8fa7 100644 --- a/samples/metadata-repo-integration/gradle.properties +++ b/samples/metadata-repo-integration/gradle.properties @@ -1,4 +1,4 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 h2.version = 2.2.220 netty.version = 4.1.80.Final logback.version = 1.4.4 diff --git a/samples/metadata-repo-integration/pom.xml b/samples/metadata-repo-integration/pom.xml index 30f584467..7762e1a51 100644 --- a/samples/metadata-repo-integration/pom.xml +++ b/samples/metadata-repo-integration/pom.xml @@ -51,8 +51,8 @@ 1.8 UTF-8 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 2.2.220 4.1.80.Final 1.4.12 diff --git a/samples/multi-project-with-tests/gradle.properties b/samples/multi-project-with-tests/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/multi-project-with-tests/gradle.properties +++ b/samples/multi-project-with-tests/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/multi-project-with-tests/pom.xml b/samples/multi-project-with-tests/pom.xml index b8deacacc..6023748ae 100644 --- a/samples/multi-project-with-tests/pom.xml +++ b/samples/multi-project-with-tests/pom.xml @@ -58,8 +58,8 @@ 1.8 UTF-8 5.10.0 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 example-app org.graalvm.demo.Application diff --git a/samples/native-config-integration/gradle.properties b/samples/native-config-integration/gradle.properties index d2b31cecc..fd386ef5d 100644 --- a/samples/native-config-integration/gradle.properties +++ b/samples/native-config-integration/gradle.properties @@ -1,3 +1,3 @@ -native.gradle.plugin.version = 0.10.2 +native.gradle.plugin.version = 0.10.3 junit.jupiter.version = 5.10.0 junit.platform.version = 1.10.0 diff --git a/samples/native-config-integration/pom.xml b/samples/native-config-integration/pom.xml index 0958156e0..c3b55d4e3 100644 --- a/samples/native-config-integration/pom.xml +++ b/samples/native-config-integration/pom.xml @@ -51,8 +51,8 @@ 1.8 UTF-8 - 0.10.2 - 0.10.2 + 0.10.3 + 0.10.3 example-app org.graalvm.example.Application