Install Required GraalVM Components (#1651)

Project manager ensures that the required 
GraalVM components are installed.
This commit is contained in:
Dmitry Bushev 2021-04-07 18:19:24 +03:00 committed by GitHub
parent 65e9cca5a4
commit ec66117cc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 501 additions and 1 deletions

View File

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

View File

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

View File

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

View File

@ -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(())
}

View File

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

View File

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

View File

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

View File

@ -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")
}

View File

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

View File

@ -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(_))
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
}
}
}

View File

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

View File

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