--jvm tries to find Java executable system-wide. (#11500)

Fixes `--jvm` option, given to the native image. This was failing on my machine, because when given `--jvm` option, the runner was trying to find the `java` executable from the distribution manager's runtime (on my system located in `~/.local/share/enso/runtime`) and it used the first runtime found. But the first runtime on my system is JDK 17.

The `--jvm` option now tries to:
- Find a JDK from the distribution manager that has the same version as the JDK used for building the engine.
- If there is not an exact version match, it tries to find a runtime from distribution manager that is *newer*.
- If none, fallback to system-wide search
- System-wide search tries to find `java` from `$JAVA_HOME` and from `$PATH`. But this is just a fallback.

# Important Notes
- Added test to Engine CI jobs that pass `--jvm` argument to a native image of engine-runner
- ea3af5ffbc
- `runtime-version-manager` sbt project migrated to a JPMS module
- `engine-runner` now depends on `runtime-version-manager`.
- Removed unnecessary stuff in `runtime-version-manager` dealing with outdated `gu` Graal Updater utility.
- Extracted [GraalVersionManager](1455b025cb/lib/scala/runtime-version-manager/src/main/java/org/enso/runtimeversionmanager/components/GraalVersionManager.java) from [RuntimeVersionManager](d2e8994700/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeVersionManager.scala)
This commit is contained in:
Pavel Marek 2024-11-18 23:44:54 +01:00 committed by GitHub
parent d5f0e9ed8c
commit 9a49a02e3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 453 additions and 889 deletions

View File

@ -721,6 +721,7 @@ lazy val componentModulesPaths =
(`runtime-instrument-runtime-server` / Compile / exportedModuleBin).value, (`runtime-instrument-runtime-server` / Compile / exportedModuleBin).value,
(`runtime-language-arrow` / Compile / exportedModuleBin).value, (`runtime-language-arrow` / Compile / exportedModuleBin).value,
(`runtime-language-epb` / Compile / exportedModuleBin).value, (`runtime-language-epb` / Compile / exportedModuleBin).value,
(`runtime-version-manager` / Compile / exportedModuleBin).value,
(`persistance` / Compile / exportedModuleBin).value, (`persistance` / Compile / exportedModuleBin).value,
(`cli` / Compile / exportedModuleBin).value, (`cli` / Compile / exportedModuleBin).value,
(`json-rpc-server` / Compile / exportedModuleBin).value, (`json-rpc-server` / Compile / exportedModuleBin).value,
@ -1613,7 +1614,8 @@ lazy val `version-output` = (project in file("lib/scala/version-output"))
defaultDevEnsoVersion = defaultDevEnsoVersion, defaultDevEnsoVersion = defaultDevEnsoVersion,
ensoVersion = ensoVersion, ensoVersion = ensoVersion,
scalacVersion = scalacVersion, scalacVersion = scalacVersion,
graalVersion = graalVersion, graalVersion = graalMavenPackagesVersion,
javaVersion = graalVersion,
currentEdition = currentEdition currentEdition = currentEdition
) )
}.taskValue }.taskValue
@ -3525,6 +3527,7 @@ lazy val `engine-runner` = project
(`pkg` / Compile / exportedModule).value, (`pkg` / Compile / exportedModule).value,
(`engine-runner-common` / Compile / exportedModule).value, (`engine-runner-common` / Compile / exportedModule).value,
(`runtime-parser` / Compile / exportedModule).value, (`runtime-parser` / Compile / exportedModule).value,
(`runtime-version-manager` / Compile / exportedModule).value,
(`version-output` / Compile / exportedModule).value, (`version-output` / Compile / exportedModule).value,
(`engine-common` / Compile / exportedModule).value, (`engine-common` / Compile / exportedModule).value,
(`polyglot-api` / Compile / exportedModule).value, (`polyglot-api` / Compile / exportedModule).value,
@ -3700,6 +3703,7 @@ lazy val `engine-runner` = project
.dependsOn(`distribution-manager`) .dependsOn(`distribution-manager`)
.dependsOn(`edition-updater`) .dependsOn(`edition-updater`)
.dependsOn(`runtime-parser`) .dependsOn(`runtime-parser`)
.dependsOn(`runtime-version-manager`)
.dependsOn(`logging-service`) .dependsOn(`logging-service`)
.dependsOn(`logging-service-logback` % Runtime) .dependsOn(`logging-service-logback` % Runtime)
.dependsOn(`engine-runner-common`) .dependsOn(`engine-runner-common`)
@ -4337,15 +4341,34 @@ lazy val `connected-lock-manager-server` = project
lazy val `runtime-version-manager` = project lazy val `runtime-version-manager` = project
.in(file("lib/scala/runtime-version-manager")) .in(file("lib/scala/runtime-version-manager"))
.enablePlugins(JPMSPlugin)
.configs(Test) .configs(Test)
.settings( .settings(
frgaalJavaCompilerSetting, frgaalJavaCompilerSetting,
scalaModuleDependencySetting,
mixedJavaScalaProjectSetting,
resolvers += Resolver.bintrayRepo("gn0s1s", "releases"), resolvers += Resolver.bintrayRepo("gn0s1s", "releases"),
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion, "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
"org.apache.commons" % "commons-compress" % commonsCompressVersion, "org.apache.commons" % "commons-compress" % commonsCompressVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test, "org.scalatest" %% "scalatest" % scalatestVersion % Test
akkaHttp ),
Compile / moduleDependencies ++= Seq(
"org.apache.commons" % "commons-compress" % commonsCompressVersion,
"org.slf4j" % "slf4j-api" % slf4jVersion
),
Compile / internalModuleDependencies := Seq(
(`cli` / Compile / exportedModule).value,
(`distribution-manager` / Compile / exportedModule).value,
(`downloader` / Compile / exportedModule).value,
(`editions` / Compile / exportedModule).value,
(`edition-updater` / Compile / exportedModule).value,
(`logging-utils` / Compile / exportedModule).value,
(`pkg` / Compile / exportedModule).value,
(`semver` / Compile / exportedModule).value,
(`scala-libs-wrapper` / Compile / exportedModule).value,
(`scala-yaml` / Compile / exportedModule).value,
(`version-output` / Compile / exportedModule).value
) )
) )
.dependsOn(pkg) .dependsOn(pkg)

View File

@ -664,6 +664,7 @@ pub async fn runner_sanity_test(
.bin .bin
.join("enso") .join("enso")
.with_executable_extension(); .with_executable_extension();
let test_base = Command::new(&enso) let test_base = Command::new(&enso)
.args(["--run", repo_root.test.join("Base_Tests").as_str()]) .args(["--run", repo_root.test.join("Base_Tests").as_str()])
.set_env(ENSO_DATA_DIRECTORY, engine_package)? .set_env(ENSO_DATA_DIRECTORY, engine_package)?
@ -686,7 +687,25 @@ pub async fn runner_sanity_test(
.run_ok() .run_ok()
.await; .await;
test_base.and(test_internal_base).and(test_geo) let all_cmds = test_base.and(test_internal_base).and(test_geo);
// The following test does not actually run anything, it just checks if the engine
// can accept `--jvm` argument and evaluates something.
if TARGET_OS != OS::Windows {
let test_jvm_arg = Command::new(&enso)
.args([
"--jvm",
"--run",
repo_root.test.join("Base_Tests").as_str(),
"__NON_EXISTING_TEST__",
])
.set_env(ENSO_DATA_DIRECTORY, engine_package)?
.run_ok()
.await;
all_cmds.and(test_jvm_arg)
} else {
all_cmds
}
} else { } else {
Ok(()) Ok(())
} }

View File

@ -36,21 +36,6 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-actor_2.13-2.6.20`. Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-actor_2.13-2.6.20`.
'akka-http-core_2.13', licensed under the Apache-2.0, is distributed with the launcher.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-http-core_2.13-10.2.10`.
'akka-http_2.13', licensed under the Apache-2.0, is distributed with the launcher.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-http_2.13-10.2.10`.
'akka-parsing_2.13', licensed under the Apache-2.0, is distributed with the launcher.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-parsing_2.13-10.2.10`.
'akka-slf4j_2.13', licensed under the Apache-2.0, is distributed with the launcher. 'akka-slf4j_2.13', licensed under the Apache-2.0, is distributed with the launcher.
The license file can be found at `licenses/APACHE2.0`. The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-slf4j_2.13-2.6.20`. Copyright notices related to this dependency can be found in the directory `com.typesafe.akka.akka-slf4j_2.13-2.6.20`.

View File

@ -1,11 +0,0 @@
Copyright (C) 2008-2017 Bjoern Hoehrmann <bjoern@hoehrmann.de>
Copyright (C) 2009-2017 Mathias Doenitz, Alexander Myltsev
Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright 2011 Mark Harrah, Eugene Yokota
Copyright 2014 Twitter, Inc.
Copyright 2015 Heiko Seeberger

View File

@ -1 +0,0 @@
Copyright (C) 2009-2020 Lightbend Inc. <http://www.lightbend.com>

View File

@ -1,7 +0,0 @@
Copyright (C) 2009-2017 Mathias Doenitz, Alexander Myltsev
Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
Copyright (c) 2011-13 Miles Sabin

View File

@ -39,10 +39,6 @@ object DefaultManagers {
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
TemporaryDirectoryManager(distributionManager, defaultResourceManager) TemporaryDirectoryManager(distributionManager, defaultResourceManager)
/** Default [[RuntimeComponentConfiguration]]. */
lazy val componentConfig: RuntimeComponentConfiguration =
new GraalVMComponentConfiguration
/** Creates a [[RuntimeVersionManager]] that uses the default distribution. */ /** Creates a [[RuntimeVersionManager]] that uses the default distribution. */
def runtimeVersionManager( def runtimeVersionManager(
globalCLIOptions: GlobalCLIOptions, globalCLIOptions: GlobalCLIOptions,
@ -55,12 +51,11 @@ object DefaultManagers {
alwaysInstallMissing alwaysInstallMissing
), ),
distributionManager, distributionManager,
new GraalVersionManager(distributionManager, LauncherEnvironment),
temporaryDirectoryManager, temporaryDirectoryManager,
defaultResourceManager, defaultResourceManager,
EngineRepository.defaultEngineReleaseProvider, EngineRepository.defaultEngineReleaseProvider,
GraalCEReleaseProvider.default, GraalCEReleaseProvider.default,
componentConfig,
RuntimeComponentUpdaterFactory.Default,
InstallerKind.Launcher InstallerKind.Launcher
) )
} }

View File

@ -9,6 +9,7 @@ module org.enso.runner {
requires org.enso.logging.config; requires org.enso.logging.config;
requires org.enso.logging.utils; requires org.enso.logging.utils;
requires org.enso.runtime.parser; requires org.enso.runtime.parser;
requires org.enso.runtime.version.manager;
requires org.enso.runner.common; requires org.enso.runner.common;
requires org.enso.pkg; requires org.enso.pkg;
requires org.enso.polyglot.api; requires org.enso.polyglot.api;

View File

@ -0,0 +1,110 @@
package org.enso.runner;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import org.enso.distribution.DistributionManager;
import org.enso.distribution.Environment;
import org.enso.runtimeversionmanager.components.GraalRuntime;
import org.enso.runtimeversionmanager.components.GraalVMVersion;
import org.enso.runtimeversionmanager.components.GraalVersionManager;
import org.enso.version.BuildVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Utility class that tries to find installed JDK on the system. */
final class JavaFinder {
private static final Logger logger = LoggerFactory.getLogger(JavaFinder.class);
private JavaFinder() {}
/**
* Tries to find {@code java} executable on the system. If a system-wide JDK is not found, tries
* to find it in the {@link DistributionManager distribution} runtimes.
*
* @return null if cannot be found. Otherwise, returns the absolute path to the executable, or
* simply {@code java} if it is on the {@code PATH}.
*/
static String findJavaExecutable() {
var javaInRuntime = findJavaExecutableInDistributionRuntimes();
if (javaInRuntime != null) {
return javaInRuntime.toAbsolutePath().toString();
}
logger.warn("No appropriate JDK found in the distribution runtimes. Trying system-wide JDK.");
var javaHome = System.getenv("JAVA_HOME");
if (javaHome != null) {
var binDir = Path.of(javaHome).resolve("bin");
Path javaExe;
if (isOnWindows()) {
javaExe = binDir.resolve("java.exe");
} else {
javaExe = binDir.resolve("java");
}
if (javaExe.toFile().exists()) {
logger.warn("Found JDK in JAVA_HOME: {}", javaHome);
return javaExe.toAbsolutePath().toString();
}
}
logger.warn("No JDK found in JAVA_HOME. Trying java on PATH.");
if (isJavaOnPath()) {
var javaExe = isOnWindows() ? "java.exe" : "java";
logger.warn("Falling back to java on PATH: {}", javaExe);
return javaExe;
}
logger.warn("No JDK found on PATH. Cannot start the runtime.");
return null;
}
private static boolean isOnWindows() {
return System.getProperty("os.name").equals("windows");
}
/**
* Tries to find {@code java} executable in the distribution runtime with the same version that
* was used for building, or a newer one.
*
* @return null if not found.
*/
private static Path findJavaExecutableInDistributionRuntimes() {
var env = new Environment() {};
var distributionManager = new DistributionManager(env);
var graalVersionManager = new GraalVersionManager(distributionManager, env);
var versionUsedForBuild =
new GraalVMVersion(BuildVersion.graalVersion(), BuildVersion.javaVersion());
var runtimeWithExactVersionMatch = graalVersionManager.findGraalRuntime(versionUsedForBuild);
if (runtimeWithExactVersionMatch != null) {
return runtimeWithExactVersionMatch.javaExecutable();
}
// Try to find newer runtime (JDK).
var newerRuntime =
graalVersionManager.getAllRuntimes().stream()
.sorted(Comparator.comparing(GraalRuntime::version))
.filter(runtime -> runtime.version().compareTo(versionUsedForBuild) > 0)
.findFirst();
if (newerRuntime.isPresent()) {
logger.warn(
"Found newer JDK [{}] than the one used for build [{}]",
newerRuntime.get().version(),
versionUsedForBuild);
return newerRuntime.get().javaExecutable();
}
return null;
}
private static boolean isJavaOnPath() {
try {
ProcessBuilder processBuilder;
if (isOnWindows()) {
processBuilder = new ProcessBuilder("java.exe", "-h");
} else {
processBuilder = new ProcessBuilder("java", "-h");
}
Process process = processBuilder.start();
boolean exitSucc = process.waitFor(5L, TimeUnit.SECONDS);
return exitSucc;
} catch (IOException | InterruptedException e) {
return false;
}
}
}

View File

@ -1339,22 +1339,13 @@ public class Main {
println(JVM_OPTION + " option has no effect - already running in JVM " + current); println(JVM_OPTION + " option has no effect - already running in JVM " + current);
} else { } else {
var commandAndArgs = new ArrayList<String>(); var commandAndArgs = new ArrayList<String>();
JVM_FOUND:
if (jvm == null) { if (jvm == null) {
var env = new Environment() {}; var javaExe = JavaFinder.findJavaExecutable();
var dm = new DistributionManager(env); if (javaExe == null) {
var paths = dm.paths(); println("Cannot find java executable");
var files = paths.runtimes().toFile().listFiles(); throw exitFail();
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(javaExe);
}
commandAndArgs.add("java");
} else { } else {
commandAndArgs.add(new File(new File(new File(jvm), "bin"), "java").getAbsolutePath()); commandAndArgs.add(new File(new File(new File(jvm), "bin"), "java").getAbsolutePath());
} }

View File

@ -52,12 +52,6 @@ object DefaultDistributionConfiguration
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
TemporaryDirectoryManager(distributionManager, resourceManager) TemporaryDirectoryManager(distributionManager, resourceManager)
lazy val componentConfiguration: RuntimeComponentConfiguration =
new GraalVMComponentConfiguration
lazy val runtimeComponentUpdaterFactory: RuntimeComponentUpdaterFactory =
RuntimeComponentUpdaterFactory.Default
/** @inheritdoc */ /** @inheritdoc */
def engineReleaseProvider: ReleaseProvider[EngineRelease] = def engineReleaseProvider: ReleaseProvider[EngineRelease] =
EngineRepository.defaultEngineReleaseProvider EngineRepository.defaultEngineReleaseProvider
@ -70,12 +64,12 @@ object DefaultDistributionConfiguration
environment = this.environment, environment = this.environment,
userInterface = userInterface, userInterface = userInterface,
distributionManager = distributionManager, distributionManager = distributionManager,
graalVersionManager =
new GraalVersionManager(distributionManager, environment),
temporaryDirectoryManager = temporaryDirectoryManager, temporaryDirectoryManager = temporaryDirectoryManager,
resourceManager = resourceManager, resourceManager = resourceManager,
engineReleaseProvider = engineReleaseProvider, engineReleaseProvider = engineReleaseProvider,
runtimeReleaseProvider = GraalCEReleaseProvider.default, runtimeReleaseProvider = GraalCEReleaseProvider.default,
componentConfig = componentConfiguration,
componentUpdaterFactory = runtimeComponentUpdaterFactory,
installerKind = InstallerKind.ProjectManager installerKind = InstallerKind.ProjectManager
) )

View File

@ -7,7 +7,7 @@ import org.enso.editions.updater.EditionManager
import java.nio.file.Path import java.nio.file.Path
import org.enso.projectmanager.versionmanagement.DistributionConfiguration import org.enso.projectmanager.versionmanagement.DistributionConfiguration
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMComponentConfiguration, GraalVersionManager,
InstallerKind, InstallerKind,
RuntimeVersionManagementUserInterface, RuntimeVersionManagementUserInterface,
RuntimeVersionManager RuntimeVersionManager
@ -28,7 +28,6 @@ import org.enso.runtimeversionmanager.releases.{
import org.enso.runtimeversionmanager.runner.{JVMSettings, JavaCommand} import org.enso.runtimeversionmanager.runner.{JVMSettings, JavaCommand}
import org.enso.runtimeversionmanager.test.{ import org.enso.runtimeversionmanager.test.{
FakeEnvironment, FakeEnvironment,
NoopComponentUpdaterFactory,
TestLocalLockManager TestLocalLockManager
} }
import org.enso.testkit.HasTestDirectory import org.enso.testkit.HasTestDirectory
@ -60,6 +59,9 @@ class TestDistributionConfiguration(
lazy val distributionManager = new DistributionManager(environment) lazy val distributionManager = new DistributionManager(environment)
lazy val graalVersionManager =
new GraalVersionManager(distributionManager, environment)
lazy val lockManager = new TestLocalLockManager lazy val lockManager = new TestLocalLockManager
lazy val resourceManager = new ResourceManager(lockManager) lazy val resourceManager = new ResourceManager(lockManager)
@ -69,10 +71,6 @@ class TestDistributionConfiguration(
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
TemporaryDirectoryManager(distributionManager, resourceManager) TemporaryDirectoryManager(distributionManager, resourceManager)
lazy val componentConfig = new GraalVMComponentConfiguration
lazy val componentUpdaterFactory = NoopComponentUpdaterFactory
override def makeRuntimeVersionManager( override def makeRuntimeVersionManager(
userInterface: RuntimeVersionManagementUserInterface userInterface: RuntimeVersionManagementUserInterface
): RuntimeVersionManager = new RuntimeVersionManager( ): RuntimeVersionManager = new RuntimeVersionManager(
@ -80,11 +78,10 @@ class TestDistributionConfiguration(
userInterface = userInterface, userInterface = userInterface,
distributionManager = distributionManager, distributionManager = distributionManager,
temporaryDirectoryManager = temporaryDirectoryManager, temporaryDirectoryManager = temporaryDirectoryManager,
graalVersionManager = graalVersionManager,
resourceManager = resourceManager, resourceManager = resourceManager,
engineReleaseProvider = engineReleaseProvider, engineReleaseProvider = engineReleaseProvider,
runtimeReleaseProvider = runtimeReleaseProvider, runtimeReleaseProvider = runtimeReleaseProvider,
componentConfig = componentConfig,
componentUpdaterFactory = componentUpdaterFactory,
installerKind = InstallerKind.ProjectManager installerKind = InstallerKind.ProjectManager
) )

View File

@ -1,20 +0,0 @@
package org.enso.runtimeversionmanager.test
import org.enso.runtimeversionmanager.components.{
GraalVMComponent,
RuntimeComponentUpdater
}
import scala.util.Try
/** Test component updater that does not do anything. */
object NoopComponentUpdater extends RuntimeComponentUpdater {
/** @inheritdoc */
override def list(): Try[Seq[GraalVMComponent]] =
Try(Seq())
/** @inheritdoc */
override def install(components: Seq[GraalVMComponent]): Try[Unit] =
Try(())
}

View File

@ -1,15 +0,0 @@
package org.enso.runtimeversionmanager.test
import org.enso.runtimeversionmanager.components.{
GraalRuntime,
RuntimeComponentUpdater,
RuntimeComponentUpdaterFactory
}
/** Test factory creating a noop updater. */
object NoopComponentUpdaterFactory extends RuntimeComponentUpdaterFactory {
/** @inheritdoc */
override def build(runtime: GraalRuntime): RuntimeComponentUpdater =
NoopComponentUpdater
}

View File

@ -9,7 +9,7 @@ import org.enso.distribution.{
} }
import org.enso.pkg.{Config, PackageManager} import org.enso.pkg.{Config, PackageManager}
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMComponentConfiguration, GraalVersionManager,
InstallerKind, InstallerKind,
RuntimeVersionManagementUserInterface, RuntimeVersionManagementUserInterface,
RuntimeVersionManager RuntimeVersionManager
@ -51,22 +51,21 @@ class RuntimeVersionManagerTest
): (DistributionManager, RuntimeVersionManager, Environment) = { ): (DistributionManager, RuntimeVersionManager, Environment) = {
val env = fakeInstalledEnvironment(environmentOverrides) val env = fakeInstalledEnvironment(environmentOverrides)
val distributionManager = new PortableDistributionManager(env) val distributionManager = new PortableDistributionManager(env)
val graalVersionManager = new GraalVersionManager(distributionManager, env)
val resourceManager = TestLocalResourceManager.create() val resourceManager = TestLocalResourceManager.create()
val temporaryDirectoryManager = val temporaryDirectoryManager =
TemporaryDirectoryManager(distributionManager, resourceManager) TemporaryDirectoryManager(distributionManager, resourceManager)
val componentConfig = new GraalVMComponentConfiguration
val runtimeVersionManager = new RuntimeVersionManager( val runtimeVersionManager = new RuntimeVersionManager(
env, env,
userInterface, userInterface,
distributionManager, distributionManager,
graalVersionManager,
temporaryDirectoryManager, temporaryDirectoryManager,
resourceManager, resourceManager,
engineProvider, engineProvider,
runtimeProvider, runtimeProvider,
componentConfig,
NoopComponentUpdaterFactory,
installerKind installerKind
) )

View File

@ -9,8 +9,8 @@ import org.enso.distribution.{
TemporaryDirectoryManager TemporaryDirectoryManager
} }
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMComponentConfiguration,
GraalVMVersion, GraalVMVersion,
GraalVersionManager,
InstallerKind, InstallerKind,
Manifest, Manifest,
RuntimeVersionManager RuntimeVersionManager
@ -140,19 +140,18 @@ class ConcurrencyTest
} }
} }
val graalVersionManager = new GraalVersionManager(distributionManager, env)
val temporaryDirectoryManager = val temporaryDirectoryManager =
TemporaryDirectoryManager(distributionManager, resourceManager) TemporaryDirectoryManager(distributionManager, resourceManager)
val componentConfig = new GraalVMComponentConfiguration
val componentsManager = new RuntimeVersionManager( val componentsManager = new RuntimeVersionManager(
env, env,
TestRuntimeVersionManagementUserInterface.default, TestRuntimeVersionManagementUserInterface.default,
distributionManager, distributionManager,
graalVersionManager,
temporaryDirectoryManager, temporaryDirectoryManager,
resourceManager, resourceManager,
engineProvider, engineProvider,
runtimeProvider, runtimeProvider,
componentConfig,
NoopComponentUpdaterFactory,
InstallerKind.Launcher InstallerKind.Launcher
) )

View File

@ -0,0 +1,22 @@
module org.enso.runtime.version.manager {
requires scala.library;
requires org.apache.commons.compress;
requires org.slf4j;
requires org.enso.cli;
requires org.enso.distribution;
requires org.enso.downloader;
requires org.enso.editions;
requires org.enso.editions.updater;
requires org.enso.logging.utils;
requires org.enso.pkg;
requires org.enso.semver;
requires org.enso.scala.yaml;
// For com.typesafe.scalalogging.Logger
requires org.enso.scala.wrapper;
requires org.enso.version.output;
exports org.enso.runtimeversionmanager;
exports org.enso.runtimeversionmanager.cli;
exports org.enso.runtimeversionmanager.components;
exports org.enso.runtimeversionmanager.runner;
}

View File

@ -0,0 +1,140 @@
package org.enso.runtimeversionmanager.components;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.enso.distribution.DistributionManager;
import org.enso.distribution.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.jdk.javaapi.CollectionConverters;
/**
* Utility class that finds installed managed runtimes (Graal JDK) from {@link DistributionManager}.
*/
public final class GraalVersionManager {
private final DistributionManager distributionManager;
private final Environment environment;
private static final Logger logger = LoggerFactory.getLogger(GraalVersionManager.class);
public GraalVersionManager(DistributionManager distributionManager, Environment environment) {
this.distributionManager = distributionManager;
this.environment = environment;
}
/**
* Get all locally installed runtimes.
*
* @return Possibly empty list. Not null.
*/
public List<GraalRuntime> getAllRuntimes() {
var foundRuntimes = new ArrayList<GraalRuntime>();
for (var runtimeSearchPath :
CollectionConverters.asJava(distributionManager.paths().runtimeSearchPaths())) {
if (runtimeSearchPath.toFile().isDirectory()) {
var subdirs = runtimeSearchPath.toFile().listFiles();
assert subdirs != null;
for (var subdir : subdirs) {
var parsedVersion = parseGraalRuntimeVersionString(subdir.getName());
if (parsedVersion != null) {
var foundRuntime = new GraalRuntime(parsedVersion, subdir.toPath());
foundRuntime.ensureValid();
foundRuntimes.add(foundRuntime);
}
}
} else {
logger.warn("Runtime search path `{}` is not a directory", runtimeSearchPath);
}
}
return foundRuntimes;
}
/**
* Tries to find a GraalVM runtime for the provided engine.
*
* <p>Returns null if the runtime is missing.
*/
public GraalRuntime findGraalRuntime(Engine engine) {
return findGraalRuntime(engine.manifest().runtimeVersion());
}
/**
* Finds an installed GraalVM runtime with the given {@code version}.
*
* <p>Returns null if that version is not installed.
*/
public GraalRuntime findGraalRuntime(GraalVMVersion version) {
var explicitPathOpt = environment.getEnvPath("ENSO_JVM_PATH");
if (explicitPathOpt.isDefined()) {
var runtime = new GraalRuntime(version, explicitPathOpt.get());
runtime.ensureValid();
logger.debug("Found GraalVM runtime [{}]", runtime);
return runtime;
}
var pathOpt = findGraalRuntimeOnSearchPath(version);
if (pathOpt != null) {
GraalRuntime runtime;
try {
runtime = loadGraalRuntime(pathOpt);
} catch (Exception e) {
throw new UnrecognizedComponentError(
"The runtime "
+ version
+ "is already installed, but cannot be "
+ "loaded due to "
+ e.getMessage()
+ "."
+ "Until the launcher gets an auto-repair "
+ "feature, please try reinstalling the runtime by "
+ "uninstalling all engines that use it and installing them "
+ "again, or manually removing `"
+ pathOpt
+ "`",
e);
}
logger.debug("Found GraalVM runtime [{}]", runtime);
return runtime;
}
logger.debug("GraalVM runtime [{}] not found", version);
return null;
}
public GraalRuntime loadGraalRuntime(Path path) throws UnrecognizedComponentError {
logger.debug("Loading Graal runtime [{}]", path);
var name = path.getFileName().toString();
var version = parseGraalRuntimeVersionString(name);
if (version == null) {
throw new UnrecognizedComponentError("Invalid runtime component name `" + name + "`", null);
}
var runtime = new GraalRuntime(version, path);
runtime.ensureValid();
return runtime;
}
private Path findGraalRuntimeOnSearchPath(GraalVMVersion version) {
var name = graalRuntimeNameForVersion(version);
for (var runtimeSearchPath :
CollectionConverters.asJava(distributionManager.paths().runtimeSearchPaths())) {
var path = runtimeSearchPath.resolve(name);
if (path.toFile().exists()) {
return path;
}
}
return null;
}
private GraalVMVersion parseGraalRuntimeVersionString(String name) {
var pattern = Pattern.compile("graalvm-ce-java(.+)-(.+)");
var matcher = pattern.matcher(name);
if (matcher.matches()) {
return new GraalVMVersion(matcher.group(2), matcher.group(1));
}
logger.warn("Unrecognized runtime name `{}`", name);
return null;
}
private static String graalRuntimeNameForVersion(GraalVMVersion version) {
return "graalvm-ce-java" + version.javaVersion() + "-" + version.graalVersion();
}
}

View File

@ -1 +0,0 @@
package org.enso.runtimeversionmanager;

View File

@ -1,6 +1,5 @@
package org.enso.runtimeversionmanager.cli package org.enso.runtimeversionmanager.cli
import akka.http.scaladsl.model.{IllegalUriException, Uri}
import org.enso.cli.arguments.{Argument, OptsParseError} import org.enso.cli.arguments.{Argument, OptsParseError}
import org.enso.logger.LoggerUtils import org.enso.logger.LoggerUtils
@ -9,14 +8,6 @@ import java.net.URISyntaxException
import org.slf4j.event.Level import org.slf4j.event.Level
object Arguments { object Arguments {
implicit val uriAkkaArgument: Argument[Uri] = (string: String) =>
try {
Right(Uri(string))
} catch {
case error: IllegalUriException =>
Left(OptsParseError(s"`$string` is not a valid Uri: $error."))
}
implicit val uriArgument: Argument[URI] = (string: String) => implicit val uriArgument: Argument[URI] = (string: String) =>
try { try {
Right(URI.create(string)) Right(URI.create(string))

View File

@ -1,11 +0,0 @@
package org.enso.runtimeversionmanager.components
/** A component of the GraalVM distribution. */
case class GraalVMComponent(id: String)
object GraalVMComponent {
val js: GraalVMComponent = GraalVMComponent("js")
val python: GraalVMComponent = GraalVMComponent("python")
val R: GraalVMComponent = GraalVMComponent("R")
}

View File

@ -1,89 +0,0 @@
package org.enso.runtimeversionmanager.components
import org.enso.cli.OS
/** Component configuration of the GraalVM distribution. */
class GraalVMComponentConfiguration extends RuntimeComponentConfiguration {
import GraalVMComponentConfiguration._
/** @inheritdoc */
override def getRequiredComponents(
version: GraalVMVersion,
os: OS
): Seq[GraalVMComponent] = {
val optPythonComponent =
if (os.hasPythonSupport) Seq(GraalVMComponent.python) else Seq()
val optRComponent =
if (os.hasRSupport) Seq(GraalVMComponent.R) else Seq()
if (version.isUnchained) {
Seq()
} else {
version.graalVersion match {
case GraalVersions.Major(v) if v >= 23 =>
// Since 23.0.0, R is not bundled in the Graal release anymore.
Seq(GraalVMComponent.js) ++ optPythonComponent
case GraalVersions.Major(v) if v >= 22 =>
Seq(GraalVMComponent.js) ++ optRComponent ++ optPythonComponent
case GraalVersions.Major(v)
if v > 20 && os.hasSulongSupport && os.hasRSupport =>
Seq(GraalVMComponent.R) ++ optPythonComponent
case _ =>
Seq()
}
}
}
}
object GraalVMComponentConfiguration {
/** OS extensions. */
implicit private class OSExtensions(os: OS) {
/** Check if the provided OS supports Sulong runtime.
*
* Sulong is a Graal sub-project, providing an engine for running
* LLVM bitcode on GraalVM.
*
* @return `true` if the OS supports Sulong runtime and `false` otherwise
*/
def hasSulongSupport: Boolean =
os match {
case OS.Linux => true
case OS.MacOS => true
case OS.Windows => false
}
/** Check if the provided OS supports Python.
* Python is currently not supported in any form on Windows.
*
* @return `true` if the OS supports Python runtime
*/
def hasPythonSupport: Boolean =
os match {
case OS.Windows => false
case _ => true
}
/** Check if the provided OS supports FastR.
* FastR is currently not supported in any form on Windows.
*
* @return `true` if the OS supports FastR GraalVM component.
*/
def hasRSupport: Boolean =
os match {
case OS.Windows => false
case _ => true
}
}
private object GraalVersions {
/** Get the major Graal version number. */
object Major {
def unapply(version: String): Option[Int] = {
version.takeWhile(_ != '.').toIntOption
}
}
}
}

View File

@ -1,180 +0,0 @@
package org.enso.runtimeversionmanager.components
import java.nio.file.Path
import com.typesafe.scalalogging.Logger
import scala.sys.process._
import scala.util.{Failure, Success, Try}
/** Module that manages components of the GraalVM distribution.
*
* @param runtime the GraalVM runtime
*/
class GraalVMComponentUpdater(runtime: GraalRuntime)
extends RuntimeComponentUpdater {
import GraalVMComponentUpdater._
private val logger = Logger[GraalVMComponentUpdater]
private val gu = runtime.findExecutable("gu")
/** Path to the GraalVM's updater.
*
* @return path that will be executed to call the updater
*/
protected def updaterExec: Path = gu
/** List the installed GraalVM components.
*
* @return the list of installed GraalVM components
*/
override def list(): Try[Seq[GraalVMComponent]] = {
val command = Seq("list", "-v")
logger.trace("{} {}", gu, Properties(gu))
logger.debug(
"Executing: JAVA_HOME={} GRAALVM_HOME={} {} {}",
runtime.javaHome,
runtime.javaHome,
gu,
command.mkString(" ")
)
val executor = new ExponentialBackoffRetry(5, logger) {
override def cmd: String = "list"
override def executeProcess(
logger: ProcessLogger
): Try[LazyList[String]] = {
val process = Process(
updaterExec.toAbsolutePath.toString +: command,
Some(runtime.javaHome.toFile),
("JAVA_HOME", runtime.javaHome),
("GRAALVM_HOME", runtime.javaHome)
)
Try(process.lazyLines(logger))
}
}
executor
.execute()
.map(stdout => if (stdout.isEmpty) Seq() else ListOut.parse(stdout))
}
/** Install the provided GraalVM components.
*
* @param components the list of components to install
*/
override def install(components: Seq[GraalVMComponent]): Try[Unit] = {
if (components.nonEmpty) {
val command = "install" +: components.map(_.id)
logger.trace("{} {}", gu, Properties(gu))
logger.debug(
"Executing: JAVA_HOME={} GRRAALVM_HOME={} {} {}",
runtime.javaHome,
runtime.javaHome,
gu,
command.mkString(" ")
)
val executor = new ExponentialBackoffRetry(5, logger) {
override def cmd: String = "install"
override def executeProcess(
logger: ProcessLogger
): Try[LazyList[String]] = {
val process = Process(
updaterExec.toAbsolutePath.toString +: command,
Some(runtime.path.toFile),
("JAVA_HOME", runtime.javaHome),
("GRAALVM_HOME", runtime.javaHome)
)
Try(process.lazyLines(logger))
}
}
executor.execute().map { stdout =>
stdout.foreach(logger.trace(_))
()
}
} else {
Success(())
}
}
}
object GraalVMComponentUpdater {
abstract class ProcessWithRetries(maxRetries: Int, logger: Logger) {
def executeProcess(logger: ProcessLogger): Try[LazyList[String]]
def cmd: String
def execute(): Try[List[String]] = execute(0)
protected def retryWait(retry: Int): Long
private def execute(retry: Int): Try[List[String]] = {
val errors = scala.collection.mutable.ListBuffer[String]()
val processLogger = ProcessLogger(err => errors.addOne(err))
executeProcess(processLogger) match {
case Success(stdout) =>
Try(stdout.toList).recoverWith({
case _ if retry < maxRetries =>
try {
Thread.sleep(retryWait(retry))
} catch {
case _: InterruptedException =>
}
execute(retry + 1)
})
case Failure(exception) if retry < maxRetries =>
logger.warn("{} failed: {}. Retrying...", cmd, exception.getMessage)
try {
Thread.sleep(retryWait(retry))
} catch {
case _: InterruptedException =>
}
execute(retry + 1)
case Failure(exception) =>
errors.foreach(logger.trace("[stderr] {}", _))
Failure(exception)
}
}
}
abstract class ExponentialBackoffRetry(maxRetries: Int, logger: Logger)
extends ProcessWithRetries(maxRetries, logger) {
override def retryWait(retry: Int): Long = {
200 * 2.toLong ^ retry
}
}
implicit private def pathToString(path: Path): String =
path.toAbsolutePath.toString
/** Debug file properties. */
private case class Properties(path: Path) {
private val file = path.toFile
override def toString: String =
s"{ exists=${file.exists()}, " +
s"executable=${file.canExecute} " +
"}"
}
/** Parser for the `gu list -v` command output. */
object ListOut {
private val ID: String = "ID"
private val separator: Char = ':'
/** Extract the GraalVM components from the gu output.
*
* @param lines the gu output
* @return the list of GraalVM components.
*/
def parse(lines: Seq[String]): Seq[GraalVMComponent] =
lines
.filter(_.startsWith(ID))
.map(_.dropWhile(_ != separator).drop(1).trim)
.map(GraalVMComponent(_))
}
}

View File

@ -11,7 +11,8 @@ import org.enso.semver.SemVer
* Can be specified either as a single integer or as a * Can be specified either as a single integer or as a
* semantic version * semantic version
*/ */
case class GraalVMVersion(graalVersion: String, javaVersion: String) { case class GraalVMVersion(graalVersion: String, javaVersion: String)
extends Comparable[GraalVMVersion] {
require(GraalVMVersion.isCorrectVersionFormat(graalVersion)) require(GraalVMVersion.isCorrectVersionFormat(graalVersion))
require(GraalVMVersion.isCorrectVersionFormat(javaVersion)) require(GraalVMVersion.isCorrectVersionFormat(javaVersion))
@ -29,15 +30,35 @@ case class GraalVMVersion(graalVersion: String, javaVersion: String) {
} }
} }
/** The GraalVM distribution policy changed a lot since GraalVM 23.1.0 for JDK 21. override def compareTo(other: GraalVMVersion): Int = {
* Most of the components for the newest GraalVM distributions are distributed as val javaSemVer = SemVer.parse(javaVersion)
* artifacts from the Maven central. This mens there is no longer `gu` tool. val otherJavaSemVer = SemVer.parse(other.javaVersion)
* if (javaSemVer.isSuccess && otherJavaSemVer.isSuccess) {
* @see https://medium.com/graalvm/truffle-unchained-13887b77b62c val comp = javaSemVer.get.compareTo(otherJavaSemVer.get)
* @return true if this version is associated with Truffle unchained. if (comp != 0) {
*/ return comp
def isUnchained: Boolean = { }
javaMajorVersion >= 21 && graalMajorVersion >= 23 }
val graalSemVer = SemVer.parse(graalVersion)
val otherGraalSemVer = SemVer.parse(other.graalVersion)
if (graalSemVer.isSuccess && otherGraalSemVer.isSuccess) {
val comp = graalSemVer.get.compareTo(otherGraalSemVer.get)
if (comp != 0) {
return comp
}
}
val javaMajorComp = javaMajorVersion.compareTo(other.javaMajorVersion)
if (javaMajorComp != 0) {
return javaMajorComp
}
val graalMajorComp = graalMajorVersion.compareTo(other.graalMajorVersion)
if (graalMajorComp != 0) {
return graalMajorComp
}
0
} }
} }

View File

@ -1,19 +0,0 @@
package org.enso.runtimeversionmanager.components
import org.enso.cli.OS
/** Provides configuration of the runtime components. */
trait RuntimeComponentConfiguration {
/** Return the list of components required for the provided version of
* the runtime installed on the provided OS.
*
* @param version the runtime version
* @param os the operating system
* @return the list of required components
*/
def getRequiredComponents(
version: GraalVMVersion,
os: OS
): Seq[GraalVMComponent]
}

View File

@ -1,19 +0,0 @@
package org.enso.runtimeversionmanager.components
import scala.util.Try
/** Module that manages components of the runtime distribution. */
trait RuntimeComponentUpdater {
/** List the installed runtime components.
*
* @return the list of installed runtime components
*/
def list(): Try[Seq[GraalVMComponent]]
/** Install the provided runtime components.
*
* @param components the list of components to install
*/
def install(components: Seq[GraalVMComponent]): Try[Unit]
}

View File

@ -1,30 +0,0 @@
package org.enso.runtimeversionmanager.components
/** The factory that creates a runtime component updater. */
trait RuntimeComponentUpdaterFactory {
/** Create a runtime component updater.
*
* @param runtime the GraalVM runtime
* @return new instance of the runtime component updater
*/
def build(runtime: GraalRuntime): RuntimeComponentUpdater
}
object RuntimeComponentUpdaterFactory {
/** The default runtime component updater factory creating an instance of
* [[GraalVMComponentUpdater]].
*/
object Default extends RuntimeComponentUpdaterFactory {
/** @inheritdoc */
override def build(runtime: GraalRuntime): RuntimeComponentUpdater = {
if (runtime.version.isUnchained) {
new UnchainedGraalVMComponentUpdater()
} else {
new GraalVMComponentUpdater(runtime)
}
}
}
}

View File

@ -3,7 +3,6 @@ package org.enso.runtimeversionmanager.components
import java.nio.file.{Files, Path, StandardOpenOption} import java.nio.file.{Files, Path, StandardOpenOption}
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.semver.SemVer import org.enso.semver.SemVer
import org.enso.cli.OS
import org.enso.distribution.{ import org.enso.distribution.{
DistributionManager, DistributionManager,
Environment, Environment,
@ -13,7 +12,6 @@ import org.enso.distribution.{
import org.enso.distribution.locking.{LockType, ResourceManager} import org.enso.distribution.locking.{LockType, ResourceManager}
import org.enso.runtimeversionmanager.CurrentVersion import org.enso.runtimeversionmanager.CurrentVersion
import org.enso.distribution.FileSystem.PathSyntax import org.enso.distribution.FileSystem.PathSyntax
import org.enso.logger.masking.MaskedPath
import org.enso.downloader.archive.Archive import org.enso.downloader.archive.Archive
import org.enso.runtimeversionmanager.locking.Resources import org.enso.runtimeversionmanager.locking.Resources
import org.enso.runtimeversionmanager.releases.ReleaseProvider import org.enso.runtimeversionmanager.releases.ReleaseProvider
@ -32,79 +30,36 @@ import scala.util.{Failure, Success, Try, Using}
* @param userInterface a [[RuntimeVersionManagementUserInterface]] instance * @param userInterface a [[RuntimeVersionManagementUserInterface]] instance
* that specifies how to handle user interactions * that specifies how to handle user interactions
* (displaying progress and handling corner cases) * (displaying progress and handling corner cases)
* @param distributionManager the [[DistributionManager]] to use
* @param engineReleaseProvider the provider of engine releases * @param engineReleaseProvider the provider of engine releases
* @param runtimeReleaseProvider the provider of runtime releases * @param runtimeReleaseProvider the provider of runtime releases
* @param componentConfig the runtime component configuration
* @param componentUpdaterFactory the runtime component updater factory
*/ */
class RuntimeVersionManager( class RuntimeVersionManager(
environment: Environment, environment: Environment,
userInterface: RuntimeVersionManagementUserInterface, userInterface: RuntimeVersionManagementUserInterface,
distributionManager: DistributionManager, distributionManager: DistributionManager,
graalVersionManager: GraalVersionManager,
temporaryDirectoryManager: TemporaryDirectoryManager, temporaryDirectoryManager: TemporaryDirectoryManager,
resourceManager: ResourceManager, resourceManager: ResourceManager,
engineReleaseProvider: ReleaseProvider[EngineRelease], engineReleaseProvider: ReleaseProvider[EngineRelease],
runtimeReleaseProvider: GraalVMRuntimeReleaseProvider, runtimeReleaseProvider: GraalVMRuntimeReleaseProvider,
componentConfig: RuntimeComponentConfiguration,
componentUpdaterFactory: RuntimeComponentUpdaterFactory,
implicit private val installerKind: InstallerKind implicit private val installerKind: InstallerKind
) { ) {
private val logger = Logger[RuntimeVersionManager] private val logger = Logger[RuntimeVersionManager]
private val os = OS.operatingSystem
/** Tries to find a GraalVM runtime for the provided engine. /** Tries to find a GraalVM runtime for the provided engine.
* *
* Returns None if the runtime is missing. * Returns None if the runtime is missing.
*/ */
def findGraalRuntime(engine: Engine): Option[GraalRuntime] = def findGraalRuntime(engine: Engine): Option[GraalRuntime] = {
findGraalRuntime(engine.manifest.runtimeVersion) Option(graalVersionManager.findGraalRuntime(engine))
}
/** Finds an installed GraalVM runtime with the given `version`. /** Finds an installed GraalVM runtime with the given `version`.
* *
* Returns None if that version is not installed. * Returns None if that version is not installed.
*/ */
def findGraalRuntime(version: GraalVMVersion): Option[GraalRuntime] = { def findGraalRuntime(version: GraalVMVersion): Option[GraalRuntime] = {
val explicitPathOpt = this.environment.getEnvPath("ENSO_JVM_PATH") Option(graalVersionManager.findGraalRuntime(version));
val graalRuntimeOpt = explicitPathOpt
.map(path => {
val runtime = GraalRuntime(version, path)
runtime.ensureValid()
runtime
})
.orElse {
val pathOpt = findGraalRuntimeOnSearchPath(version)
pathOpt.map { path =>
// TODO [RW] for now an exception is thrown if the installation is
// corrupted, in #1052 offer to repair the broken installation
loadGraalRuntime(path).recoverWith { case e: Exception =>
Failure(
UnrecognizedComponentError(
s"The runtime $version is already installed, but cannot be " +
s"loaded due to $e. Until the launcher gets an auto-repair " +
s"feature, please try reinstalling the runtime by " +
s"uninstalling all engines that use it and installing them " +
s"again, or manually removing `$path`",
e
)
)
}.get
}
}
graalRuntimeOpt match {
case Some(graalRuntime) =>
logger.debug("Found GraalVM runtime [{}]", graalRuntime)
case None =>
logger.debug("GraalVM runtime [{}] not found", version)
}
graalRuntimeOpt
}
private def findGraalRuntimeOnSearchPath(
version: GraalVMVersion
): Option[Path] = {
val name = graalRuntimeNameForVersion(version)
firstExisting(distributionManager.paths.runtimeSearchPaths.map(_ / name))
} }
/** Executes the provided action with a requested engine version. /** Executes the provided action with a requested engine version.
@ -635,70 +590,13 @@ class RuntimeVersionManager(
private def engineNameForVersion(version: SemVer): String = private def engineNameForVersion(version: SemVer): String =
version.toString version.toString
/** Returns name of the directory containing the runtime of that version.
*/
private def graalRuntimeNameForVersion(version: GraalVMVersion): String = {
s"graalvm-ce-java${version.javaVersion}-${version.graalVersion}"
}
/** Loads the GraalVM runtime definition. /** Loads the GraalVM runtime definition.
*/ */
private def loadGraalRuntime(path: Path): Try[GraalRuntime] = { private def loadGraalRuntime(path: Path): Try[GraalRuntime] = {
logger.debug("Loading Graal runtime [{}]", path) try {
val name = path.getFileName.toString Success(graalVersionManager.loadGraalRuntime(path))
for { } catch {
version <- parseGraalRuntimeVersionString(name) case e: UnrecognizedComponentError => Failure(e)
.toRight(
UnrecognizedComponentError(s"Invalid runtime component name `$name`.")
)
.toTry
runtime = GraalRuntime(version, path)
_ <- runtime.ensureValid()
_ <- installRequiredRuntimeComponents(runtime).recover {
case NonFatal(error) =>
val msg = translateError(error)
logger.warn(
"Failed to install required components on the existing [{}]. " +
"Some language features may be unavailable. {}",
runtime,
msg
)
}
} yield runtime
}
/** Provide human-readable error messages for OS-specific failures
* @param cause exception thrown when executing the process
* @return human-readable error message
*/
private def translateError(cause: Throwable): String = {
val msg = cause.getMessage
OS.operatingSystem match {
case OS.Linux => msg
case OS.MacOS => msg
case OS.Windows =>
if (msg.contains("-1073741515")) {
"Required Microsoft Visual C++ installation is missing"
} else {
msg
}
}
}
/** Gets the runtime version from its name.
*/
private def parseGraalRuntimeVersionString(
name: String
): Option[GraalVMVersion] = {
val regex = """graalvm-ce-java(.+)-(.+)""".r
name match {
case regex(javaVersionString, graalVersionString) =>
Some(GraalVMVersion(graalVersionString, javaVersionString))
case _ =>
logger.warn(
s"Unrecognized runtime name `$name`"
)
None
} }
} }
@ -800,7 +698,7 @@ class RuntimeVersionManager(
try { try {
logger.debug("Loading temporary runtime [{}]", runtimeTemporaryPath) logger.debug("Loading temporary runtime [{}]", runtimeTemporaryPath)
val temporaryRuntime =
loadGraalRuntime(runtimeTemporaryPath).recoverWith { error => loadGraalRuntime(runtimeTemporaryPath).recoverWith { error =>
Failure( Failure(
InstallationError( InstallationError(
@ -809,17 +707,7 @@ class RuntimeVersionManager(
error error
) )
) )
}.get }
logger.debug("Installing GraalVM components to [{}]", temporaryRuntime)
installRequiredRuntimeComponents(temporaryRuntime).recoverWith {
error =>
Failure(
InstallationError(
"fatal: Cannot install the required runtime components",
error
)
)
}.get
val runtimePath = val runtimePath =
distributionManager.paths.runtimes / runtimeDirectoryName distributionManager.paths.runtimes / runtimeDirectoryName
@ -849,32 +737,6 @@ class RuntimeVersionManager(
} }
} }
/** Install components required for the specified runtime on the specified OS.
*
* @param runtime the GraalVM runtime
*/
private def installRequiredRuntimeComponents(
runtime: GraalRuntime
): Try[Unit] = {
logger.debug("Installing GraalVM components [{}, {}]", runtime, os)
val cu = componentUpdaterFactory.build(runtime)
val requiredComponents =
componentConfig.getRequiredComponents(runtime.version, os)
if (requiredComponents.isEmpty) Success(())
else {
for {
installedComponents <- cu.list()
_ = logger.debug(
"Available GraalVM components: [{}]",
installedComponents
)
missingComponents = requiredComponents.diff(installedComponents)
_ <- cu.install(missingComponents)
} yield ()
}
}
private def engineDirectoryNameForVersion(version: SemVer): Path = private def engineDirectoryNameForVersion(version: SemVer): Path =
Path.of(version.toString()) Path.of(version.toString())
@ -931,33 +793,6 @@ class RuntimeVersionManager(
FileSystem.removeDirectory(temporaryPath) FileSystem.removeDirectory(temporaryPath)
} }
/** Logs on trace level all installed engines and runtimes.
*
* NOTE: Useful for debugging but should not be added to production code since it may
* cause unnecessary installations for different engine versions.
*/
def logAvailableComponentsForDebugging(): Unit = logger.whenTraceEnabled {
logger.trace("Discovering available components...")
val engines = for (engine <- listInstalledEngines()) yield {
val runtime = findGraalRuntime(engine)
val runtimeName = runtime
.map(_.toString)
.getOrElse("no runtime found")
val broken = if (engine.isMarkedBroken) " (broken)" else ""
s" - Enso ${engine.version}$broken [runtime: $runtimeName] " +
s"[location: ${MaskedPath(engine.path).applyMasking()}]"
}
val runtimes =
for (runtime <- listInstalledGraalRuntimes())
yield s" - $runtime [location: " +
s"${MaskedPath(runtime.path).applyMasking()}]"
logger.trace(
s"Installed engines (${engines.length}):\n${engines.mkString("\n")}\n\n" +
s"Installed runtimes (${runtimes.length}):\n${runtimes.mkString("\n")}"
)
}
} }
/* Note [RuntimeVersionManager Concurrency Model] /* Note [RuntimeVersionManager Concurrency Model]

View File

@ -1,26 +0,0 @@
package org.enso.runtimeversionmanager.components
import scala.util.{Success, Try}
/** A dummy component updater for [[GraalVMVersion.isUnchained unchained]] GraalVM.
* There is no `gu` utility in unchained GraalVM, so this updater does not do anything.
* It only lists the components that are known to be included in the unchained GraalVM.
*/
class UnchainedGraalVMComponentUpdater extends RuntimeComponentUpdater {
/** There
*
* @return the list of installed runtime components
*/
override def list(): Try[Seq[GraalVMComponent]] = Success(Seq())
/** Install the provided runtime components.
*
* @param components the list of components to install
*/
override def install(components: Seq[GraalVMComponent]): Try[Unit] = {
throw new IllegalStateException(
"Cannot install components in unchained GraalVM"
)
}
}

View File

@ -1,106 +0,0 @@
package org.enso.runtimeversionmanager.components
import org.enso.cli.OS
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class GraalVMComponentConfigurationSpec extends AnyWordSpec with Matchers {
"RuntimeComponentConfiguration" should {
"return required components" in {
val conf = new GraalVMComponentConfiguration
val required = Seq(GraalVMComponent.python, GraalVMComponent.R)
val requiredAbove22 = required ++ Seq(GraalVMComponent.js)
conf.getRequiredComponents(
GraalVMVersion("21.0.0.2", "11"),
OS.Linux
) should contain theSameElementsAs required
conf.getRequiredComponents(
GraalVMVersion("21.0.0.2", "11"),
OS.MacOS
) should contain theSameElementsAs required
conf.getRequiredComponents(
GraalVMVersion("21.0.0.2", "11"),
OS.Windows
) should contain theSameElementsAs Seq()
conf.getRequiredComponents(
GraalVMVersion("22.0.0.0", "11"),
OS.Linux
) should contain theSameElementsAs requiredAbove22
conf.getRequiredComponents(
GraalVMVersion("22.3.1", "17"),
OS.MacOS
) should contain theSameElementsAs requiredAbove22
conf.getRequiredComponents(
GraalVMVersion("22.3.1", "17"),
OS.Windows
) should contain theSameElementsAs Seq(
GraalVMComponent.js
)
conf.getRequiredComponents(
GraalVMVersion("22.3.1", "17"),
OS.Linux
) should contain theSameElementsAs Seq(
GraalVMComponent.js,
GraalVMComponent.python,
GraalVMComponent.R
)
conf.getRequiredComponents(
GraalVMVersion("20.0.0.0", "11"),
OS.Linux
) should contain theSameElementsAs Seq()
conf.getRequiredComponents(
GraalVMVersion("23.0.0", "17.0.7+7.1"),
OS.Linux
) should contain theSameElementsAs Seq(
GraalVMComponent.js,
GraalVMComponent.python
)
conf.getRequiredComponents(
GraalVMVersion("23.0.0", "11"),
OS.Linux
) should contain theSameElementsAs Seq(
GraalVMComponent.js,
GraalVMComponent.python
)
conf.getRequiredComponents(
GraalVMVersion("23.0.0", "17.0.7+7.1"),
OS.Windows
) should contain theSameElementsAs Seq(
GraalVMComponent.js
)
}
"return no required components for Truffle unchained" in {
val conf = new GraalVMComponentConfiguration
val versions = Seq(
GraalVMVersion("23.0.0", "21"),
GraalVMVersion("23.0.1", "21"),
GraalVMVersion("23.0.0", "21.0.1"),
GraalVMVersion("23.0.1", "21.0.1"),
GraalVMVersion("23.0.0", "21.0.1+5.1"),
GraalVMVersion("23.0.1", "21.0.1+5.1"),
GraalVMVersion("23.1.0", "21")
)
versions.forall(_.isUnchained) shouldBe true
versions.foreach(version => {
conf.getRequiredComponents(
version,
OS.Linux
) shouldBe empty
})
}
}
}

View File

@ -1,57 +0,0 @@
package org.enso.runtimeversionmanager.components
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class GraalVMComponentParserSpec extends AnyWordSpec with Matchers {
"RuntimeComponentUpdater" should {
"parse list output" in {
val listOut =
"""
|ID : js
|Name : Graal.js
|Version : 21.0.0.2
|GraalVM : n/a
|Stability: -
|Origin :
|
|ID : graalvm
|Name : GraalVM Core
|Version : 21.0.0.2
|GraalVM : n/a
|Stability: -
|Origin :
|
|ID : R
|Name : FastR
|Version : 21.0.0.2
|GraalVM : 21.0.0.2
|Stability: Experimental
|Origin : https://github.com/oracle/fastr/releases/download/vm-21.0.0.2/r-installable-java11-linux-amd64-21.0.0.2.jar
|
|ID : llvm-toolchain
|Name : LLVM.org toolchain
|Version : 21.0.0.2
|GraalVM : 21.0.0.2
|Stability: Supported
|Origin : https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/llvm-toolchain-installable-java11-linux-amd64-21.0.0.2.jar
|
|ID : native-image
|Name : Native Image
|Version : 21.0.0.2
|GraalVM : 21.0.0.2
|Stability: Early adopter
|Origin : https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/native-image-installable-svm-java11-linux-amd64-21.0.0.2.jar
|""".stripMargin
val expectedComponents =
Seq("js", "graalvm", "R", "llvm-toolchain", "native-image")
.map(GraalVMComponent(_))
val components =
GraalVMComponentUpdater.ListOut.parse(listOut.linesIterator.toSeq)
components shouldEqual expectedComponents
}
}
}

View File

@ -0,0 +1,40 @@
package org.enso.runtimeversionmanager.components
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class GraalVMVersionSpec extends AnyWordSpec with Matchers {
"GraalVM Version" should {
"greater JDK version is considered newer" in {
val graalVersion = "24.0.0"
val ver1 = GraalVMVersion(graalVersion, "21.0.2")
val ver2 = GraalVMVersion(graalVersion, "17.0.7")
(ver1.compareTo(ver2) > 0) shouldBe true
}
"If JDK version is same (semver), greater Graal version is newer" in {
val jdkVersion = "21.0.2"
val ver1 = GraalVMVersion("24.0.2", jdkVersion)
val ver2 = GraalVMVersion("24.0.0", jdkVersion)
(ver1.compareTo(ver2) > 0) shouldBe true
}
"If JDK version is same (non semver), greater Graal version is newer" in {
val jdkVersion = "21"
val ver1 = GraalVMVersion("24.0.2", jdkVersion)
val ver2 = GraalVMVersion("24.0.0", jdkVersion)
(ver1.compareTo(ver2) > 0) shouldBe true
}
"be correctly ordered" in {
val ver1 = GraalVMVersion("24.0.0", "21.0.2")
val ver2 = GraalVMVersion("23.1.2", "21.0.2")
val ver3 = GraalVMVersion("23.1.0", "21.0.1")
val ver4 = GraalVMVersion("23.0.0", "17.0.7")
val lst = List(ver4, ver2, ver3, ver1)
val sortedList = lst.sortWith((ver1, ver2) => ver1.compareTo(ver2) < 0)
sortedList shouldEqual List(ver4, ver3, ver2, ver1)
}
}
}

View File

@ -12,6 +12,8 @@ public class VersionsTest {
assertTrue(SemVer.parse("0.1.1").isSuccess()); assertTrue(SemVer.parse("0.1.1").isSuccess());
assertTrue(SemVer.parse("9999.0.0").isSuccess()); assertTrue(SemVer.parse("9999.0.0").isSuccess());
assertTrue(SemVer.parse("0.1").isFailure()); assertTrue(SemVer.parse("0.1").isFailure());
assertTrue(SemVer.parse("21.0.2").isSuccess());
assertTrue(SemVer.parse("17.0.7").isSuccess());
} }
@Test @Test

View File

@ -19,10 +19,23 @@ public class BuildVersion {
return GeneratedVersion.scalacVersion(); return GeneratedVersion.scalacVersion();
} }
/**
* Version of GraalVM, more specifically, version of the GraalVM and Truffle libraries used to
* build the engine.
*/
public static String graalVersion() { public static String graalVersion() {
return GeneratedVersion.graalVersion(); return GeneratedVersion.graalVersion();
} }
/**
* Version of Java (JDK) used to build the engine.
*
* @return
*/
public static String javaVersion() {
return GeneratedVersion.javaVersion();
}
public static String currentEdition() { public static String currentEdition() {
return GeneratedVersion.currentEdition(); return GeneratedVersion.currentEdition();
} }

View File

@ -18,6 +18,7 @@ object BuildInfo {
* @param ensoVersion Enso version * @param ensoVersion Enso version
* @param scalacVersion Scala compiler version used in the project * @param scalacVersion Scala compiler version used in the project
* @param graalVersion GraalVM version used in the project * @param graalVersion GraalVM version used in the project
* @param javaVersion Java language version used in the project.
* @param currentEdition name of the edition associated with the Enso * @param currentEdition name of the edition associated with the Enso
* version; this should be removed once #1831 is * version; this should be removed once #1831 is
* implemented * implemented
@ -30,6 +31,7 @@ object BuildInfo {
ensoVersion: String, ensoVersion: String,
scalacVersion: String, scalacVersion: String,
graalVersion: String, graalVersion: String,
javaVersion: String,
currentEdition: String currentEdition: String
): Seq[File] = { ): Seq[File] = {
val gitInfo = getGitInformation(log).getOrElse(fallbackGitInformation) val gitInfo = getGitInformation(log).getOrElse(fallbackGitInformation)
@ -58,6 +60,10 @@ object BuildInfo {
| return "${graalVersion}"; | return "${graalVersion}";
| } | }
| |
| static String javaVersion() {
| return "${javaVersion}";
| }
|
| static String currentEdition() { | static String currentEdition() {
| return "${currentEdition}"; | return "${currentEdition}";
| } | }

View File

@ -1,8 +0,0 @@
Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2016-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2017-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2019-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2021-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright 2009-2020 Lightbend Inc. <http://www.lightbend.com>

View File

@ -1,6 +0,0 @@
Copyright (C) 2008-2017 Bjoern Hoehrmann <bjoern@hoehrmann.de>
Copyright (C) 2009-2017 Mathias Doenitz, Alexander Myltsev
Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright 2011 Mark Harrah, Eugene Yokota
Copyright 2014 Twitter, Inc.
Copyright 2015 Heiko Seeberger

View File

@ -1,6 +0,0 @@
Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2017-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>

View File

@ -1 +0,0 @@
Copyright (C) 2009-2020 Lightbend Inc. <http://www.lightbend.com>

View File

@ -1,2 +0,0 @@
Copyright (C) 2019-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (c) 2013-14 Miles Sabin

View File

@ -1,4 +0,0 @@
Copyright (C) 2009-2017 Mathias Doenitz, Alexander Myltsev
Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
Copyright (c) 2011-13 Miles Sabin

View File

@ -1,3 +1,3 @@
5BE1C47CDA76AF7E6C42089993BEA2B8DB544C1752AE5D8055BF2B4E713B20EA BBB2B5E440C388A022983CB2C0B9B4BA68D04B97FC07E4C8E142952448437BE0
E92B79099FF706DC1F50287D428322268954285BD62F19B0A70456E3356AE1D1 CA8B1BB2992E828BA958FD6CFC0076B213170FCD454406131407ED0340EC7F2F
0 0