Support for --jvm option in Enso runner (#10374)

Addresses one of two concerns of #5298 - adds support for `--jvm` argument to allow us to switch from _native image_ built Enso binary (as developed by #10126) to regular JVM based Enso execution. This change _doesn't affect production builds_. The _native executable_ continues to be only built by `engine-runner/buildNativeImage` which is tested on CI, but not in the production jobs.
This commit is contained in:
Jaroslav Tulach 2024-07-06 09:02:20 +02:00 committed by GitHub
parent d65371096b
commit 515d8238bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 276 additions and 110 deletions

1
.gitignore vendored
View File

@ -133,7 +133,6 @@ build-cache/
##################
*.build_artifacts.txt
/runner
######################
## Enso-Development ##

View File

@ -59,6 +59,12 @@
[10353]: https://github.com/enso-org/enso/pull/10353
[10396]: https://github.com/enso-org/enso/pull/10396
#### 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]

103
build.sbt
View File

@ -2627,55 +2627,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)
@ -3575,6 +3580,10 @@ ThisBuild / buildEngineDistribution := {
buildEngineDistribution.result.value
}
ThisBuild / engineDistributionRoot := {
engineDistributionRoot.value
}
lazy val buildEngineDistributionNoIndex =
taskKey[Unit]("Builds the engine distribution without generating indexes")
buildEngineDistributionNoIndex := {

View File

@ -123,7 +123,6 @@
bench-report.xml:
build.sbt:
run:
runner: # The runner native image (Linux only).
CHANGELOG.md:
# Launcher Package

View File

@ -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)?

View File

@ -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<Path>, ir_caches: IrCaches) -> Result<Command> {

View File

@ -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

View File

@ -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%

View File

@ -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 <arguments>
```
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.

View File

@ -8,6 +8,7 @@ import java.net.URISyntaxException;
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 @@ import scala.runtime.BoxedUnit;
/** 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 @@ public final class Main {
.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 @@ public final class Main {
options
.addOption(help)
.addOption(repl)
.addOption(jvm)
.addOption(run)
.addOption(inspect)
.addOption(dumpGraphs)
@ -508,17 +520,17 @@ public final class Main {
}
/** 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 @@ public final class Main {
}
/** 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 @@ public final class Main {
}
/** 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 @@ public final class Main {
*
* @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 @@ public final class Main {
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<String>();
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) {
@ -1315,15 +1412,18 @@ public final class Main {
}
}
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 {

View File

@ -2,10 +2,14 @@ package org.enso.interpreter;
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 class OptionsHelper {
}
/**
* 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.
*
* <p>This is used mostly for the runtime tests, as language home is not normally defined there.
*/
public static Optional<String> getLanguageHomeOverride(TruffleLanguage.Env env) {
public static Optional<String> 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<String> getEditionOverride(TruffleLanguage.Env env) {

View File

@ -174,8 +174,7 @@ public final class EnsoContext {
},
res -> res));
Optional<String> 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);

View File

@ -35,7 +35,7 @@ public final class Parser implements AutoCloseable {
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 @@ public final class Parser implements AutoCloseable {
}
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);
}
}

View File

@ -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",

View File

@ -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/" +