diff --git a/.gitignore b/.gitignore index f5da0aeb7242..84a8be89dfe6 100644 --- a/.gitignore +++ b/.gitignore @@ -133,7 +133,6 @@ build-cache/ ################## *.build_artifacts.txt -/runner ###################### ## Enso-Development ## diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9f850549ee..baf7410954cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,12 @@ [10352]: https://github.com/enso-org/enso/pull/10352 [10353]: https://github.com/enso-org/enso/pull/10353 +#### Enso Language & Runtime + +- Support for [explicit --jvm option][10374] when launching `enso` CLI + +[10374]: https://github.com/enso-org/enso/pull/10374 + #### Enso Standard Library - [Added Statistic.Product][10122] diff --git a/build.sbt b/build.sbt index f563a3455610..90c898b9ef09 100644 --- a/build.sbt +++ b/build.sbt @@ -2614,55 +2614,60 @@ lazy val `engine-runner` = project assembly := assembly .dependsOn(`runtime-fat-jar` / assembly) .value, - rebuildNativeImage := - NativeImage - .buildNativeImage( - "runner", - staticOnLinux = false, - additionalOptions = Seq( - "-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.NoOpLog", - "-H:IncludeResources=.*Main.enso$", - "-H:+AddAllCharsets", - "-H:+IncludeAllLocales", - "-ea", - // useful perf & debug switches: - // "-g", - // "-H:+SourceLevelDebug", - // "-H:-DeleteLocalSymbols", - // you may need to set smallJdk := None to use following flags: - // "--trace-class-initialization=org.enso.syntax2.Parser", - "-Dnic=nic" - ), - mainClass = Some("org.enso.runner.Main"), - initializeAtRuntime = Seq( - "org.jline.nativ.JLineLibrary", - "org.jline.terminal.impl.jna", - "io.methvin.watchservice.jna.CarbonAPI", - "zio.internal.ZScheduler$$anon$4", - "org.enso.runner.Main$", - "sun.awt", - "sun.java2d", - "sun.font", - "java.awt", - "com.sun.imageio", - "com.sun.jna.internal.Cleaner", - "com.sun.jna.Structure$FFIType", - "akka.http" + rebuildNativeImage := Def + .taskDyn { + NativeImage + .buildNativeImage( + "enso", + targetDir = engineDistributionRoot.value / "bin", + staticOnLinux = false, + additionalOptions = Seq( + "-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.NoOpLog", + "-H:IncludeResources=.*Main.enso$", + "-H:+AddAllCharsets", + "-H:+IncludeAllLocales", + "-ea", + // useful perf & debug switches: + // "-g", + // "-H:+SourceLevelDebug", + // "-H:-DeleteLocalSymbols", + // you may need to set smallJdk := None to use following flags: + // "--trace-class-initialization=org.enso.syntax2.Parser", + "-Dnic=nic" + ), + mainClass = Some("org.enso.runner.Main"), + initializeAtRuntime = Seq( + "org.jline.nativ.JLineLibrary", + "org.jline.terminal.impl.jna", + "io.methvin.watchservice.jna.CarbonAPI", + "zio.internal.ZScheduler$$anon$4", + "org.enso.runner.Main$", + "sun.awt", + "sun.java2d", + "sun.font", + "java.awt", + "com.sun.imageio", + "com.sun.jna.internal.Cleaner", + "com.sun.jna.Structure$FFIType", + "akka.http" + ) ) - ) - .dependsOn(NativeImage.additionalCp) - .dependsOn(NativeImage.smallJdk) - .dependsOn(assembly) - .dependsOn( - buildEngineDistribution - ) - .value, - buildNativeImage := NativeImage - .incrementalNativeImageBuild( - rebuildNativeImage, - "runner" + } + .dependsOn(NativeImage.additionalCp) + .dependsOn(NativeImage.smallJdk) + .dependsOn(assembly) + .dependsOn( + buildEngineDistribution ) - .value + .value, + buildNativeImage := Def.taskDyn { + NativeImage + .incrementalNativeImageBuild( + rebuildNativeImage, + "enso", + targetDir = engineDistributionRoot.value / "bin" + ) + }.value ) .dependsOn(`version-output`) .dependsOn(yaml) @@ -3559,6 +3564,10 @@ ThisBuild / buildEngineDistribution := { buildEngineDistribution.result.value } +ThisBuild / engineDistributionRoot := { + engineDistributionRoot.value +} + lazy val buildEngineDistributionNoIndex = taskKey[Unit]("Builds the engine distribution without generating indexes") buildEngineDistributionNoIndex := { diff --git a/build/build/paths.yaml b/build/build/paths.yaml index e07d94cfe387..b6ff88b881ea 100644 --- a/build/build/paths.yaml +++ b/build/build/paths.yaml @@ -123,7 +123,6 @@ bench-report.xml: build.sbt: run: - runner: # The runner native image (Linux only). CHANGELOG.md: # Launcher Package diff --git a/build/build/src/engine/context.rs b/build/build/src/engine/context.rs index 3cd76ce446cd..ea7f13d92b9b 100644 --- a/build/build/src/engine/context.rs +++ b/build/build/src/engine/context.rs @@ -483,7 +483,15 @@ impl RunContext { if self.config.build_native_runner { debug!("Building and testing native engine runners"); runner_sanity_test(&self.repo_root, None).await?; - ide_ci::fs::remove_file_if_exists(&self.repo_root.runner)?; + let enso = self + .repo_root + .built_distribution + .enso_engine_triple + .engine_package + .bin + .join("enso") + .with_executable_extension(); + ide_ci::fs::remove_file_if_exists(&enso)?; if self.config.build_espresso_runner { let enso_java = "espresso"; sbt.command()? @@ -636,7 +644,14 @@ pub async fn runner_sanity_test( // The engine package is necessary for running the native runner. ide_ci::fs::tokio::require_exist(engine_package).await?; if enso_java.is_none() { - let test_base = Command::new(&repo_root.runner) + let enso = repo_root + .built_distribution + .enso_engine_triple + .engine_package + .bin + .join("enso") + .with_executable_extension(); + let test_base = Command::new(&enso) .args(["--run", repo_root.test.join("Base_Tests").as_str()]) .set_env_opt(ENSO_JAVA, enso_java)? .set_env(ENSO_DATA_DIRECTORY, engine_package)? diff --git a/build/build/src/enso.rs b/build/build/src/enso.rs index 03e66565f83c..656e40f7df44 100644 --- a/build/build/src/enso.rs +++ b/build/build/src/enso.rs @@ -73,11 +73,21 @@ impl BuiltEnso { } pub async fn run_benchmarks(&self, opt: BenchmarkOptions) -> Result { - self.cmd()? - .with_args(["--run", self.paths.repo_root.test.benchmarks.as_str()]) + let filename = format!("enso{}", if TARGET_OS == OS::Windows { ".exe" } else { "" }); + let enso = self + .paths + .repo_root + .built_distribution + .enso_engine_triple + .engine_package + .bin + .join(filename); + let benchmarks = Command::new(&enso) + .args(["--jvm", "--run", self.paths.repo_root.test.benchmarks.as_str()]) .set_env(ENSO_BENCHMARK_TEST_DRY_RUN, &Boolean::from(opt.dry_run))? .run_ok() - .await + .await; + benchmarks } pub fn run_test(&self, test_path: impl AsRef, ir_caches: IrCaches) -> Result { diff --git a/distribution/bin/enso b/distribution/bin/enso index 3638e0a7721d..81691b368715 100755 --- a/distribution/bin/enso +++ b/distribution/bin/enso @@ -8,5 +8,5 @@ for opt in "$@"; do done JAVA_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED $JAVA_OPTS" -exec java --module-path $COMP_PATH -Dorg.graalvm.language.enso.home=$COMP_PATH $EXTRA_OPTS $JAVA_OPTS -m org.enso.runtime/org.enso.EngineRunnerBootLoader "$@" +exec java --module-path $COMP_PATH $EXTRA_OPTS $JAVA_OPTS -m org.enso.runtime/org.enso.EngineRunnerBootLoader "$@" exit diff --git a/distribution/bin/enso.bat b/distribution/bin/enso.bat index 5387dd0073a0..65652caaf0e1 100644 --- a/distribution/bin/enso.bat +++ b/distribution/bin/enso.bat @@ -7,5 +7,5 @@ set EXTRA_OPTS=%EXTRA_OPTS% -Dgraal.Dump=Truffle:1 ) ) set JAVA_OPTS=%JAVA_OPTS% --add-opens=java.base/java.nio=ALL-UNNAMED -java --module-path %comp-dir% -Dorg.graalvm.language.enso.home=%comp-dir% -Dpolyglot.compiler.IterativePartialEscape=true %EXTRA_OPTS% %JAVA_OPTS% -m org.enso.runtime/org.enso.EngineRunnerBootLoader %* +java --module-path %comp-dir% -Dpolyglot.compiler.IterativePartialEscape=true %EXTRA_OPTS% %JAVA_OPTS% -m org.enso.runtime/org.enso.EngineRunnerBootLoader %* exit /B %errorlevel% diff --git a/docs/infrastructure/native-image.md b/docs/infrastructure/native-image.md index b1091285b21f..d99c3e357529 100644 --- a/docs/infrastructure/native-image.md +++ b/docs/infrastructure/native-image.md @@ -111,7 +111,7 @@ For example, to update settings for the Launcher: java -agentlib:native-image-agent=config-merge-dir=engine/launcher/src/main/resources/META-INF/native-image/org/enso/launcher -jar launcher.jar ``` -Note that for convenience, you can run the launcher/engine runner via +Note that for convenience, you can run the launcher/engine runtime via `bin/enso`, e.g. ```bash @@ -210,7 +210,7 @@ sbt> engine-runner/buildNativeImage and execute any program with that binary - for example `test/Base_Tests` ```bash -$ runner --run test/Base_Tests +$ ./built-distribution/enso-engine-*/enso-*/bin/enso --run test/Base_Tests ``` The task that generates the Native Image, along with all the necessary @@ -224,7 +224,7 @@ Since [PR-6966](https://github.com/enso-org/enso/pull/6966) there is an experimental support for including [Espresso Java interpreter](https://www.graalvm.org/jdk17/reference-manual/java-on-truffle/) to allow use of some library functions (like `IO.println`) in the _Native Image_ -built runner. +built runtime. The support can be enabled by setting environment variable `ENSO_JAVA=espresso` and making sure Espresso is installed in the Enso engine `component` directory: @@ -278,7 +278,7 @@ Espresso support works also with `ENSO_JAVA=espresso` is specified when building the `runner` executable: ```bash -enso$ rm runner +enso$ rm ./built-distribution/enso-engine-*/enso-*/bin/enso enso$ ENSO_JAVA=espresso sbt --java-home /graalvm sbt> engine-runner/buildNativeImage ``` @@ -288,7 +288,7 @@ build script detects presence of Espresso and automatically adds `--language:java` when creating the image. Then you can use ```bash -$ ENSO_JAVA=espresso ./runner --run hello.enso +$ ENSO_JAVA=espresso ./built-distribution/enso-engine-*/enso-*/bin/enso --run hello.enso ``` -to execute native image `runner` build of Enso together with Espresso. +to execute native image build of Enso together with Espresso. diff --git a/engine/runner/src/main/java/org/enso/runner/Main.java b/engine/runner/src/main/java/org/enso/runner/Main.java index 7e2dd587a1f7..5d495fa1bbbf 100644 --- a/engine/runner/src/main/java/org/enso/runner/Main.java +++ b/engine/runner/src/main/java/org/enso/runner/Main.java @@ -8,6 +8,7 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -53,6 +54,7 @@ /** The main CLI entry point class. */ public final class Main { + private static final String JVM_OPTION = "jvm"; private static final String RUN_OPTION = "run"; private static final String INSPECT_OPTION = "inspect"; private static final String DUMP_GRAPHS_OPTION = "dump-graphs"; @@ -127,6 +129,15 @@ private static Options buildOptions() { .longOpt(RUN_OPTION) .desc("Runs a specified Enso file.") .build(); + var jvm = + cliOptionBuilder() + .hasArg(true) + .numberOfArgs(1) + .optionalArg(true) + .argName("jvm") + .longOpt(JVM_OPTION) + .desc("Specifies whether to run JVM mode and optionally selects a JVM to run with.") + .build(); var inspect = cliOptionBuilder() .longOpt(INSPECT_OPTION) @@ -451,6 +462,7 @@ private static Options buildOptions() { options .addOption(help) .addOption(repl) + .addOption(jvm) .addOption(run) .addOption(inspect) .addOption(dumpGraphs) @@ -508,17 +520,17 @@ private static void printHelp(Options options) { } /** Terminates the process with a failure exit code. */ - private RuntimeException exitFail() { + private static RuntimeException exitFail() { return doExit(1); } /** Terminates the process with a success exit code. */ - private RuntimeException exitSuccess() { + private static RuntimeException exitSuccess() { return doExit(0); } /** Shuts down the logging service and terminates the process. */ - private RuntimeException doExit(int exitCode) { + private static RuntimeException doExit(int exitCode) { RunnerLogging.tearDown(); System.exit(exitCode); return null; @@ -932,7 +944,7 @@ private void displayVersion(boolean useJson) { } /** Parses the log level option. */ - private Level parseLogLevel(String levelOption) { + private static Level parseLogLevel(String levelOption) { var name = levelOption.toLowerCase(); var found = Stream.of(Level.values()).filter(x -> name.equals(x.name().toLowerCase())).findFirst(); @@ -949,7 +961,7 @@ private Level parseLogLevel(String levelOption) { } /** Parses an URI that specifies the logging service connection. */ - private URI parseUri(String string) { + private static URI parseUri(String string) { try { return new URI(string); } catch (URISyntaxException ex) { @@ -966,7 +978,7 @@ private URI parseUri(String string) { * * @param args the command line arguments */ - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { new Main().launch(args); } @@ -1294,10 +1306,95 @@ private void println(String msg) { System.out.println(msg); } - private void launch(String[] args) { + private void launch(String[] args) throws IOException, InterruptedException, URISyntaxException { var options = buildOptions(); var line = preprocessArguments(options, args); - launch(options, line); + + var logMasking = new boolean[1]; + var logLevel = setupLogging(options, line, logMasking); + + if (line.hasOption(JVM_OPTION)) { + var jvm = line.getOptionValue(JVM_OPTION); + var current = System.getProperty("java.home"); + if (jvm == null) { + jvm = current; + } + if (current == null || !current.equals(jvm)) { + var loc = Main.class.getProtectionDomain().getCodeSource().getLocation(); + var commandAndArgs = new ArrayList(); + JVM_FOUND: + if (jvm == null) { + var env = new Environment() {}; + var dm = new DistributionManager(env); + var paths = dm.paths(); + var files = paths.runtimes().toFile().listFiles(); + if (files != null) { + for (var d : files) { + var java = new File(new File(d, "bin"), "java").getAbsoluteFile(); + if (java.exists()) { + commandAndArgs.add(java.getPath()); + break JVM_FOUND; + } + } + } + commandAndArgs.add("java"); + } else { + commandAndArgs.add(new File(new File(new File(jvm), "bin"), "java").getAbsolutePath()); + } + var jvmOptions = System.getenv("JAVA_OPTS"); + if (jvmOptions != null) { + for (var op : jvmOptions.split(" ")) { + if (op.isEmpty()) { + continue; + } + commandAndArgs.add(op); + } + } + + commandAndArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED"); + commandAndArgs.add("--module-path"); + var component = new File(loc.toURI().resolve("..")).getAbsoluteFile(); + if (!component.getName().equals("component")) { + component = new File(component, "component"); + } + if (!component.isDirectory()) { + throw new IOException("Cannot find " + component + " directory"); + } + commandAndArgs.add(component.getPath()); + commandAndArgs.add("-m"); + commandAndArgs.add("org.enso.runtime/org.enso.EngineRunnerBootLoader"); + var it = line.iterator(); + while (it.hasNext()) { + var op = it.next(); + if (JVM_OPTION.equals(op.getLongOpt())) { + continue; + } + var longName = op.getLongOpt(); + if (longName != null) { + commandAndArgs.add("--" + longName); + } else { + commandAndArgs.add("-" + op.getOpt()); + } + var values = op.getValuesList(); + if (values != null) { + commandAndArgs.addAll(values); + } + } + commandAndArgs.addAll(line.getArgList()); + var pb = new ProcessBuilder(); + pb.inheritIO(); + pb.command(commandAndArgs); + var p = pb.start(); + var exitCode = p.waitFor(); + if (exitCode == 0) { + throw exitSuccess(); + } else { + throw doExit(exitCode); + } + } + } + + launch(options, line, logLevel, logMasking[0]); } protected CommandLine preprocessArguments(Options options, String[] args) { @@ -1311,15 +1408,18 @@ protected CommandLine preprocessArguments(Options options, String[] args) { } } - protected void launch(Options options, CommandLine line) { + private static Level setupLogging(Options options, CommandLine line, boolean[] logMasking) { var logLevel = scala.Option.apply(line.getOptionValue(LOG_LEVEL)) - .map(this::parseLogLevel) + .map(Main::parseLogLevel) .getOrElse(() -> defaultLogLevel); - var connectionUri = scala.Option.apply(line.getOptionValue(LOGGER_CONNECT)).map(this::parseUri); - var logMasking = !line.hasOption(NO_LOG_MASKING); - RunnerLogging.setup(connectionUri, logLevel, logMasking); + var connectionUri = scala.Option.apply(line.getOptionValue(LOGGER_CONNECT)).map(Main::parseUri); + logMasking[0] = !line.hasOption(NO_LOG_MASKING); + RunnerLogging.setup(connectionUri, logLevel, logMasking[0]); + return logLevel; + } + private void launch(Options options, CommandLine line, Level logLevel, boolean logMasking) { if (line.hasOption(LANGUAGE_SERVER_OPTION)) { runLanguageServer(line, logLevel); } else { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/OptionsHelper.java b/engine/runtime/src/main/java/org/enso/interpreter/OptionsHelper.java index 0ef341a6ee76..01da0083b1d0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/OptionsHelper.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/OptionsHelper.java @@ -2,10 +2,14 @@ import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleLanguage; +import java.io.File; +import java.net.URISyntaxException; import java.util.Optional; import org.enso.polyglot.RuntimeOptions; -public class OptionsHelper { +public final class OptionsHelper { + private OptionsHelper() {} + /** * Gets the location of the project that is the context of the current run. * @@ -22,17 +26,29 @@ public static Optional getProjectRoot(TruffleLanguage.Env env) { } /** - * Gets an optional override for the language home directory. + * Finds location of language home directory. It checks {@link + * RuntimeOptions#LANGUAGE_HOME_OVERRIDE} and uses it. If it is not specified, it derives the + * location from code source location of the JAR file. * *

This is used mostly for the runtime tests, as language home is not normally defined there. */ - public static Optional getLanguageHomeOverride(TruffleLanguage.Env env) { + public static Optional findLanguageHome(TruffleLanguage.Env env) { String option = env.getOptions().get(RuntimeOptions.LANGUAGE_HOME_OVERRIDE_KEY); - if (option.equals("")) { - return Optional.empty(); - } else { + if (!option.equals("")) { return Optional.of(option); } + try { + var cs = OptionsHelper.class.getProtectionDomain().getCodeSource(); + var runtimeJarUri = cs.getLocation().toURI(); + var runtimeJar = new File(runtimeJarUri); + var componentDir = runtimeJar.getParentFile(); + if (componentDir != null && componentDir.isDirectory()) { + return Optional.of(componentDir.getPath()); + } + } catch (IllegalStateException | URISyntaxException ex) { + // cannot derive the location + } + return Optional.empty(); } public static Optional getEditionOverride(TruffleLanguage.Env env) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 0b962b357e75..ff674c43280c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -174,8 +174,7 @@ public void initialize() { }, res -> res)); - Optional languageHome = - OptionsHelper.getLanguageHomeOverride(environment).or(() -> Optional.ofNullable(home)); + var languageHome = OptionsHelper.findLanguageHome(environment); var editionOverride = OptionsHelper.getEditionOverride(environment); var resourceManager = new org.enso.distribution.locking.ResourceManager(lockManager); diff --git a/lib/rust/parser/generate-java/java/org/enso/syntax2/Parser.java b/lib/rust/parser/generate-java/java/org/enso/syntax2/Parser.java index d2c8a75fef2f..beeafc03cad2 100644 --- a/lib/rust/parser/generate-java/java/org/enso/syntax2/Parser.java +++ b/lib/rust/parser/generate-java/java/org/enso/syntax2/Parser.java @@ -35,7 +35,7 @@ private static void initializeLibraries() { var d = root; File path = null; while (d != null) { - path = new File(d, name); + path = new File(new File(d, "component"), name); if (path.exists()) break; d = d.getParentFile(); } @@ -44,12 +44,13 @@ private static void initializeLibraries() { } System.load(path.getAbsolutePath()); } catch (NullPointerException | IllegalArgumentException | LinkageError e) { - if (!searchFromDirToTop(e, root, "target", "rust", "debug", name)) { - if (!searchFromDirToTop( - e, new File(".").getAbsoluteFile(), "target", "rust", "debug", name)) { - throw new IllegalStateException("Cannot load parser from " + root, e); - } + if (searchFromDirToTop(e, root, "target", "rust", "debug", name)) { + return; + } + if (searchFromDirToTop(e, new File(".").getAbsoluteFile(), "target", "rust", "debug", name)) { + return; } + throw new IllegalStateException("Cannot load parser from " + root, e); } } diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala index 0996182b4b4e..71dc607b3b6c 100644 --- a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala @@ -192,11 +192,8 @@ class Runner( val shouldInvokeViaModulePath = engine.graalRuntimeVersion.isUnchained val componentPath = engine.componentDirPath.toAbsolutePath.normalize - val langHomeOption = Seq( - s"-Dorg.graalvm.language.enso.home=$componentPath" - ) var jvmArguments = - manifestOptions ++ environmentOptions ++ commandLineOptions ++ langHomeOption + manifestOptions ++ environmentOptions ++ commandLineOptions if (shouldInvokeViaModulePath) { jvmArguments = jvmArguments :++ Seq( "--module-path", diff --git a/project/NativeImage.scala b/project/NativeImage.scala index a04697ebbb33..e19fad98d93b 100644 --- a/project/NativeImage.scala +++ b/project/NativeImage.scala @@ -83,8 +83,9 @@ object NativeImage { * @param verbose whether to print verbose output from the native image. */ def buildNativeImage( - artifactName: String, + name: String, staticOnLinux: Boolean, + targetDir: File = null, additionalOptions: Seq[String] = Seq.empty, buildMemoryLimitMegabytes: Option[Int] = Some(15608), runtimeThreadStackMegabytes: Option[Int] = Some(2), @@ -95,7 +96,8 @@ object NativeImage { includeRuntime: Boolean = true ): Def.Initialize[Task[Unit]] = Def .task { - val log = state.value.log + val log = state.value.log + val targetLoc = artifactFile(targetDir, name, false) def nativeImagePath(prefix: Path)(path: Path): Path = { val base = path.resolve(prefix) @@ -138,7 +140,7 @@ object NativeImage { } if (additionalOptions.contains("--language:java")) { log.warn( - s"Building ${artifactName} image with experimental Espresso support!" + s"Building ${targetLoc} image with experimental Espresso support!" ) } @@ -229,7 +231,7 @@ object NativeImage { buildMemoryLimitOptions ++ runtimeMemoryOptions ++ additionalOptions ++ - Seq("-o", artifactName) + Seq("-o", targetLoc.toString()) args = mainClass match { case Some(main) => @@ -240,8 +242,8 @@ object NativeImage { Seq("-jar", pathToJAR.toString) } - val targetDir = (Compile / target).value - val argFile = targetDir.toPath.resolve(NATIVE_IMAGE_ARG_FILE) + val targetDirValue = (Compile / target).value + val argFile = targetDirValue.toPath.resolve(NATIVE_IMAGE_ARG_FILE) IO.writeLines(argFile.toFile, args, append = false) val pathParts = pathExts ++ Option(System.getenv("PATH")).toSeq @@ -266,15 +268,16 @@ object NativeImage { sb.append(str + System.lineSeparator()) }) log.info( - s"Started building $artifactName native image. The output is captured." + s"Started building $targetLoc native image. The output is captured." ) - val retCode = process.!(processLogger) - if (retCode != 0) { - log.error("Native Image build failed, with output: ") + val retCode = process.!(processLogger) + val targetFile = artifactFile(targetDir, name, true) + if (retCode != 0 || !targetFile.exists()) { + log.error("Native Image build of $targetFile failed, with output: ") println(sb.toString()) throw new RuntimeException("Native Image build failed") } - log.info(s"$artifactName native image build successful.") + log.info(s"$targetLoc native image build successful.") } .dependsOn(Compile / compile) @@ -289,14 +292,15 @@ object NativeImage { */ def incrementalNativeImageBuild( actualBuild: TaskKey[Unit], - artifactName: String + name: String, + targetDir: File = null ): Def.Initialize[Task[Unit]] = Def.taskDyn { def rebuild(reason: String) = { streams.value.log.info( s"$reason, forcing a rebuild." ) - val artifact = artifactFile(artifactName) + val artifact = artifactFile(targetDir, name) if (artifact.exists()) { artifact.delete() } @@ -314,7 +318,7 @@ object NativeImage { sourcesDiff: ChangeReport[File] => if (sourcesDiff.modified.nonEmpty) rebuild(s"Native Image is not up to date") - else if (!artifactFile(artifactName).exists()) + else if (!artifactFile(targetDir, name).exists()) rebuild("Native Image does not exist") else Def.task { @@ -328,9 +332,20 @@ object NativeImage { /** [[File]] representing the artifact called `name` built with the Native * Image. */ - def artifactFile(name: String): File = - if (Platform.isWindows) file(name + ".exe") - else file(name) + def artifactFile( + targetDir: File, + name: String, + withExtension: Boolean = false + ): File = { + val artifactName = + if (withExtension && Platform.isWindows) name + ".exe" + else name + if (targetDir == null) { + new File(artifactName).getAbsoluteFile() + } else { + new File(targetDir, artifactName) + } + } private val muslBundleUrl = "https://github.com/gradinac/musl-bundle-example/releases/download/" +