From ec66117cc65847b486114a166574c2393dbe62f8 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 7 Apr 2021 18:19:24 +0300 Subject: [PATCH] Install Required GraalVM Components (#1651) Project manager ensures that the required GraalVM components are installed. --- .../distribution/DefaultManagers.scala | 9 ++ .../DefaultDistributionConfiguration.scala | 11 +++ .../TestDistributionConfiguration.scala | 8 ++ .../test/NoopComponentUpdater.scala | 20 ++++ .../test/NoopComponentUpdaterFactory.scala | 16 ++++ .../test/RuntimeVersionManagerTest.scala | 4 + .../locking/ConcurrencyTest.scala | 4 + .../components/GraalVMComponent.scala | 11 +++ .../GraalVMComponentConfiguration.scala | 52 ++++++++++ .../components/GraalVMComponentUpdater.scala | 94 +++++++++++++++++++ .../RuntimeComponentConfiguration.scala | 19 ++++ .../components/RuntimeComponentUpdater.scala | 19 ++++ .../RuntimeComponentUpdaterFactory.scala | 28 ++++++ .../components/RuntimeVersionManager.scala | 40 +++++++- .../GraalVMComponentConfigurationSpec.scala | 42 +++++++++ .../GraalVMComponentParserSpec.scala | 57 +++++++++++ .../GraalVMComponentUpdaterSpec.scala | 68 ++++++++++++++ 17 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdater.scala create mode 100644 lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdaterFactory.scala create mode 100644 lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponent.scala create mode 100644 lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfiguration.scala create mode 100644 lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdater.scala create mode 100644 lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentConfiguration.scala create mode 100644 lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdater.scala create mode 100644 lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdaterFactory.scala create mode 100644 lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfigurationSpec.scala create mode 100644 lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentParserSpec.scala create mode 100644 lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdaterSpec.scala diff --git a/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala b/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala index 55564c47a85e..d244e8d5af2e 100644 --- a/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala +++ b/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala @@ -5,7 +5,10 @@ import org.enso.launcher.cli.{ GlobalCLIOptions } import org.enso.runtimeversionmanager.components.{ + GraalVMComponentConfiguration, InstallerKind, + RuntimeComponentConfiguration, + RuntimeComponentUpdaterFactory, RuntimeVersionManager } import org.enso.runtimeversionmanager.distribution.{ @@ -43,6 +46,10 @@ object DefaultManagers { lazy val temporaryDirectoryManager = new TemporaryDirectoryManager(distributionManager, defaultResourceManager) + /** Default [[RuntimeComponentConfiguration]]. */ + lazy val componentConfig: RuntimeComponentConfiguration = + new GraalVMComponentConfiguration + /** Creates a [[RuntimeVersionManager]] that uses the default distribution. */ def runtimeVersionManager( globalCLIOptions: GlobalCLIOptions, @@ -58,6 +65,8 @@ object DefaultManagers { defaultResourceManager, EngineRepository.defaultEngineReleaseProvider, GraalCEReleaseProvider.default, + componentConfig, + RuntimeComponentUpdaterFactory.Default, InstallerKind.Launcher ) } diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala index 22dad296113c..fd9856cfd536 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala @@ -3,7 +3,10 @@ package org.enso.projectmanager.versionmanagement import com.typesafe.scalalogging.LazyLogging import org.enso.runtimeversionmanager.Environment import org.enso.runtimeversionmanager.components.{ + GraalVMComponentConfiguration, InstallerKind, + RuntimeComponentConfiguration, + RuntimeComponentUpdaterFactory, RuntimeVersionManagementUserInterface, RuntimeVersionManager } @@ -52,6 +55,12 @@ object DefaultDistributionConfiguration lazy val temporaryDirectoryManager = new TemporaryDirectoryManager(distributionManager, resourceManager) + lazy val componentConfiguration: RuntimeComponentConfiguration = + new GraalVMComponentConfiguration + + lazy val runtimeComponentUpdaterFactory: RuntimeComponentUpdaterFactory = + RuntimeComponentUpdaterFactory.Default + /** @inheritdoc */ def engineReleaseProvider: ReleaseProvider[EngineRelease] = EngineRepository.defaultEngineReleaseProvider @@ -67,6 +76,8 @@ object DefaultDistributionConfiguration resourceManager = resourceManager, engineReleaseProvider = engineReleaseProvider, runtimeReleaseProvider = GraalCEReleaseProvider.default, + componentConfig = componentConfiguration, + componentUpdaterFactory = runtimeComponentUpdaterFactory, installerKind = InstallerKind.ProjectManager ) diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala index 8df0e68b640c..313ba03959f8 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala @@ -4,6 +4,7 @@ import java.nio.file.Path import org.enso.projectmanager.versionmanagement.DistributionConfiguration import org.enso.runtimeversionmanager.components.{ + GraalVMComponentConfiguration, InstallerKind, RuntimeVersionManagementUserInterface, RuntimeVersionManager @@ -30,6 +31,7 @@ import org.enso.runtimeversionmanager.runner.{JVMSettings, JavaCommand} import org.enso.runtimeversionmanager.test.{ FakeEnvironment, HasTestDirectory, + NoopComponentUpdaterFactory, TestLocalLockManager } @@ -67,6 +69,10 @@ class TestDistributionConfiguration( lazy val temporaryDirectoryManager = new TemporaryDirectoryManager(distributionManager, resourceManager) + lazy val componentConfig = new GraalVMComponentConfiguration + + lazy val componentUpdaterFactory = NoopComponentUpdaterFactory + override def makeRuntimeVersionManager( userInterface: RuntimeVersionManagementUserInterface ): RuntimeVersionManager = new RuntimeVersionManager( @@ -76,6 +82,8 @@ class TestDistributionConfiguration( resourceManager = resourceManager, engineReleaseProvider = engineReleaseProvider, runtimeReleaseProvider = runtimeReleaseProvider, + componentConfig = componentConfig, + componentUpdaterFactory = componentUpdaterFactory, installerKind = InstallerKind.ProjectManager ) diff --git a/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdater.scala b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdater.scala new file mode 100644 index 000000000000..dfea11667add --- /dev/null +++ b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdater.scala @@ -0,0 +1,20 @@ +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(()) +} diff --git a/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdaterFactory.scala b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdaterFactory.scala new file mode 100644 index 000000000000..e6d0f949f9f3 --- /dev/null +++ b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/NoopComponentUpdaterFactory.scala @@ -0,0 +1,16 @@ +package org.enso.runtimeversionmanager.test + +import org.enso.runtimeversionmanager.OS +import org.enso.runtimeversionmanager.components.{ + GraalRuntime, + RuntimeComponentUpdater, + RuntimeComponentUpdaterFactory +} + +/** Test factory creating a noop updater. */ +object NoopComponentUpdaterFactory extends RuntimeComponentUpdaterFactory { + + /** @inheritdoc */ + override def build(runtime: GraalRuntime, os: OS): RuntimeComponentUpdater = + NoopComponentUpdater +} diff --git a/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala index ea4a37f1cbca..ae8dac884526 100644 --- a/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala +++ b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala @@ -6,6 +6,7 @@ import nl.gn0s1s.bump.SemVer import org.enso.pkg.{PackageManager, SemVerEnsoVersion} import org.enso.runtimeversionmanager._ import org.enso.runtimeversionmanager.components.{ + GraalVMComponentConfiguration, InstallerKind, RuntimeVersionManagementUserInterface, RuntimeVersionManager @@ -53,6 +54,7 @@ class RuntimeVersionManagerTest val resourceManager = TestLocalResourceManager.create() val temporaryDirectoryManager = new TemporaryDirectoryManager(distributionManager, resourceManager) + val componentConfig = new GraalVMComponentConfiguration val runtimeVersionManager = new RuntimeVersionManager( userInterface, @@ -61,6 +63,8 @@ class RuntimeVersionManagerTest resourceManager, engineProvider, runtimeProvider, + componentConfig, + NoopComponentUpdaterFactory, installerKind ) diff --git a/lib/scala/runtime-version-manager-test/src/test/scala/org/enso/runtimeversionmanager/locking/ConcurrencyTest.scala b/lib/scala/runtime-version-manager-test/src/test/scala/org/enso/runtimeversionmanager/locking/ConcurrencyTest.scala index 7dc469e210c5..c7f4b7e86cbe 100644 --- a/lib/scala/runtime-version-manager-test/src/test/scala/org/enso/runtimeversionmanager/locking/ConcurrencyTest.scala +++ b/lib/scala/runtime-version-manager-test/src/test/scala/org/enso/runtimeversionmanager/locking/ConcurrencyTest.scala @@ -7,6 +7,7 @@ import org.enso.cli.task.TaskProgress import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.runtimeversionmanager._ import org.enso.runtimeversionmanager.components.{ + GraalVMComponentConfiguration, GraalVMVersion, InstallerKind, Manifest, @@ -146,6 +147,7 @@ class ConcurrencyTest val temporaryDirectoryManager = new TemporaryDirectoryManager(distributionManager, resourceManager) + val componentConfig = new GraalVMComponentConfiguration val componentsManager = new RuntimeVersionManager( TestRuntimeVersionManagementUserInterface.default, distributionManager, @@ -153,6 +155,8 @@ class ConcurrencyTest resourceManager, engineProvider, runtimeProvider, + componentConfig, + NoopComponentUpdaterFactory, InstallerKind.Launcher ) diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponent.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponent.scala new file mode 100644 index 000000000000..9fa89490d0b5 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponent.scala @@ -0,0 +1,11 @@ +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") +} diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfiguration.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfiguration.scala new file mode 100644 index 000000000000..b1e4a8640db5 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfiguration.scala @@ -0,0 +1,52 @@ +package org.enso.runtimeversionmanager.components + +import org.enso.runtimeversionmanager.OS + +/** Component configuration of the GraalVM distribution. */ +class GraalVMComponentConfiguration extends RuntimeComponentConfiguration { + + import GraalVMComponentConfiguration._ + + /** @inheritdoc */ + override def getRequiredComponents( + version: GraalVMVersion, + os: OS + ): Seq[GraalVMComponent] = + version.graalVersion match { + case GraalVersions.Major(v) if v > 20 && os.hasSulongSupport => + Seq(GraalVMComponent.python, GraalVMComponent.R) + 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 + } + } + + private object GraalVersions { + + /** Get the major Graal version number. */ + object Major { + def unapply(version: String): Option[Int] = { + version.takeWhile(_ != '.').toIntOption + } + } + } +} diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdater.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdater.scala new file mode 100644 index 000000000000..d8bae8a57a3f --- /dev/null +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdater.scala @@ -0,0 +1,94 @@ +package org.enso.runtimeversionmanager.components + +import java.nio.file.Path + +import org.enso.runtimeversionmanager.OS + +import scala.sys.process._ +import scala.util.{Success, Try} + +/** Module that manages components of the GraalVM distribution. + * + * @param runtime the GraalVM runtime + * @param os the operating system + */ +class GraalVMComponentUpdater(runtime: GraalRuntime, os: OS) + extends RuntimeComponentUpdater { + + import GraalVMComponentUpdater._ + + /** List the installed GraalVM components. + * + * @return the list of installed GraalVM components + */ + override def list: Try[Seq[GraalVMComponent]] = { + val suppressStderr = ProcessLogger(_ => ()) + val process = Process( + Seq[String](Paths.gu, "list", "-v"), + Some(runtime.path.toFile), + ("JAVA_HOME", runtime.path), + ("GRAALVM_HOME", runtime.path) + ) + + for { + lines <- Try(process.lazyLines(suppressStderr)) + } yield ListOut.parse(lines) + } + + /** 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 componentsList = components.map(_.id) + val process = Process( + Seq[String](Paths.gu, "install") ++ componentsList, + Some(runtime.path.toFile), + ("JAVA_HOME", runtime.path), + ("GRAALVM_HOME", runtime.path) + ) + Try(process.!!) + } else { + Success(()) + } + } + + private object Paths { + + /** Path to `gu` executable. */ + val gu: Path = os match { + case OS.Linux => runtime.path / "bin" / "gu" + case OS.MacOS => runtime.path / "bin" / "gu" + case OS.Windows => runtime.path / "bin" / "gu.cmd" + } + } +} +object GraalVMComponentUpdater { + + implicit private def pathToString(path: Path): String = + path.toAbsolutePath.toString + + implicit private class PathExtensions(path: Path) { + + def /(child: String): Path = path.resolve(child) + } + + /** 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(_)) + } +} diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentConfiguration.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentConfiguration.scala new file mode 100644 index 000000000000..3371495a9a1a --- /dev/null +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentConfiguration.scala @@ -0,0 +1,19 @@ +package org.enso.runtimeversionmanager.components + +import org.enso.runtimeversionmanager.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] +} diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdater.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdater.scala new file mode 100644 index 000000000000..46c22dbd5f11 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdater.scala @@ -0,0 +1,19 @@ +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] +} diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdaterFactory.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdaterFactory.scala new file mode 100644 index 000000000000..b180ffe13077 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeComponentUpdaterFactory.scala @@ -0,0 +1,28 @@ +package org.enso.runtimeversionmanager.components + +import org.enso.runtimeversionmanager.OS + +/** The factory that creates a runtime component updater. */ +trait RuntimeComponentUpdaterFactory { + + /** Create a runtime component updater. + * + * @param runtime the GraalVM runtime + * @param os the operating system + * @return new instance of the runtime component updater + */ + def build(runtime: GraalRuntime, os: OS): RuntimeComponentUpdater +} + +object RuntimeComponentUpdaterFactory { + + /** The default runtime component updater factory creating an instance of + * [[GraalVMComponentUpdater]]. + */ + object Default extends RuntimeComponentUpdaterFactory { + + /** @inheritdoc */ + override def build(runtime: GraalRuntime, os: OS): RuntimeComponentUpdater = + new GraalVMComponentUpdater(runtime, os) + } +} diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeVersionManager.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeVersionManager.scala index 88bf167dfaf6..8561f219d8ec 100644 --- a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeVersionManager.scala +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/components/RuntimeVersionManager.scala @@ -4,7 +4,7 @@ import java.nio.file.{Files, Path, StandardOpenOption} import com.typesafe.scalalogging.Logger import nl.gn0s1s.bump.SemVer -import org.enso.runtimeversionmanager.{CurrentVersion, FileSystem} +import org.enso.runtimeversionmanager.{CurrentVersion, FileSystem, OS} import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.runtimeversionmanager.archive.Archive import org.enso.runtimeversionmanager.distribution.{ @@ -35,6 +35,8 @@ import scala.util.{Failure, Success, Try, Using} * @param distributionManager the [[DistributionManager]] to use * @param engineReleaseProvider the provider of engine releases * @param runtimeReleaseProvider the provider of runtime releases + * @param componentConfig the runtime component configuration + * @param componentUpdaterFactory the runtime component updater factory */ class RuntimeVersionManager( userInterface: RuntimeVersionManagementUserInterface, @@ -43,9 +45,12 @@ class RuntimeVersionManager( resourceManager: ResourceManager, engineReleaseProvider: ReleaseProvider[EngineRelease], runtimeReleaseProvider: GraalVMRuntimeReleaseProvider, + componentConfig: RuntimeComponentConfiguration, + componentUpdaterFactory: RuntimeComponentUpdaterFactory, implicit private val installerKind: InstallerKind ) { private val logger = Logger[RuntimeVersionManager] + private val os = OS.operatingSystem /** Tries to find a GraalVM runtime for the provided engine. * @@ -582,6 +587,12 @@ class RuntimeVersionManager( .toTry runtime = GraalRuntime(version, path) _ <- runtime.ensureValid() + _ <- installRequiredRuntimeComponents(runtime, os).recover { _ => + logger.warn( + s"Failed to install required components on the existing $runtime. " + + "Some language features may be unavailable." + ) + } } yield runtime } @@ -703,8 +714,15 @@ class RuntimeVersionManager( "fatal: Cannot load the installed runtime." ) } + installRequiredRuntimeComponents(runtime, os).getOrElse { + FileSystem.removeDirectory(runtimePath) + throw InstallationError( + "fatal: Cannot install the required runtime components." + ) + } userInterface.logInfo(s"Installed $runtime.") + runtime } catch { case NonFatal(e) => @@ -713,6 +731,26 @@ class RuntimeVersionManager( } } + /** Install components required for the specified runtime on the specified OS. + * + * @param runtime the GraalVM runtime + * @param os the operating system + */ + private def installRequiredRuntimeComponents( + runtime: GraalRuntime, + os: OS + ): Try[Unit] = { + val cu = componentUpdaterFactory.build(runtime, os) + val requiredComponents = + componentConfig.getRequiredComponents(runtime.version, os) + + for { + installedComponents <- cu.list + missingComponents = requiredComponents.diff(installedComponents) + _ <- cu.install(missingComponents) + } yield () + } + private def engineDirectoryNameForVersion(version: SemVer): Path = Path.of(version.toString()) diff --git a/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfigurationSpec.scala b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfigurationSpec.scala new file mode 100644 index 000000000000..6d9cef680822 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentConfigurationSpec.scala @@ -0,0 +1,42 @@ +package org.enso.runtimeversionmanager.components + +import org.enso.runtimeversionmanager.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) + + 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 required + + conf.getRequiredComponents( + GraalVMVersion("20.0.0.0", "11"), + OS.Linux + ) should contain theSameElementsAs Seq() + + } + } +} diff --git a/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentParserSpec.scala b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentParserSpec.scala new file mode 100644 index 000000000000..52cb43901723 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentParserSpec.scala @@ -0,0 +1,57 @@ +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 + } + } +} diff --git a/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdaterSpec.scala b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdaterSpec.scala new file mode 100644 index 000000000000..a0afe695c048 --- /dev/null +++ b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/components/GraalVMComponentUpdaterSpec.scala @@ -0,0 +1,68 @@ +package org.enso.runtimeversionmanager.components + +import java.nio.file.Path + +import org.enso.runtimeversionmanager.OS +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import scala.util.{Failure, Success} + +class GraalVMComponentUpdaterSpec extends AnyWordSpec with Matchers { + + val javaHomeOpt: Option[Path] = sys.env.get("JAVA_HOME").map(Path.of(_)) + val os: OS = OS.operatingSystem + + val vendorVersionOpt: Option[String] = + sys.props.get("java.vendor.version").map(_.dropWhile(!_.isDigit)) + + val javaVersionOpt: Option[String] = + sys.props.get("java.specification.version") + + val graalVMVersionOpt: Option[GraalVMVersion] = + for { + graalVersion <- vendorVersionOpt + javaVersion <- javaVersionOpt + } yield GraalVMVersion(graalVersion, javaVersion) + + val graalRuntime: Option[GraalRuntime] = + for { + javaHome <- javaHomeOpt + graalVMVersion <- graalVMVersionOpt + } yield GraalRuntime(graalVMVersion, javaHome) + + def isCI: Boolean = sys.env.contains("CI") + + def getOrElseFail[A](opt: Option[A]): A = { + if (opt.isEmpty) { + if (isCI) { + throw new Exception( + s"Error in test environment. " + + s"OS=$os; " + + s"JAVA_HOME=$javaHomeOpt; " + + s"java.vendor.version=$vendorVersionOpt; " + + s"java.specification.version=$javaVersionOpt" + ) + } else { + pending + } + } + opt.get + } + + "RuntimeComponentUpdater" should { + + "list components" in { + val graal = getOrElseFail(graalRuntime) + val ru = new GraalVMComponentUpdater(graal, os) + + ru.list match { + case Success(components) => + components should not be empty + case Failure(cause) => + fail(cause) + } + } + } + +}