diff --git a/build.sbt b/build.sbt index 09bceca9754e..814e197d7fc7 100644 --- a/build.sbt +++ b/build.sbt @@ -3786,7 +3786,6 @@ lazy val `engine-runner` = project "-H:IncludeResources=.*Main.enso$", "-H:+AddAllCharsets", "-H:+IncludeAllLocales", - "-ea", // useful perf & debug switches: // "-g", // "-H:+SourceLevelDebug", @@ -3823,7 +3822,7 @@ lazy val `engine-runner` = project .dependsOn(NativeImage.additionalCp) .dependsOn(NativeImage.smallJdk) .dependsOn( - buildEngineDistribution + createEnginePackage ) .value, buildNativeImage := Def.taskDyn { @@ -5103,9 +5102,9 @@ launcherDistributionRoot := packageBuilder.localArtifact("launcher") / "enso" projectManagerDistributionRoot := packageBuilder.localArtifact("project-manager") / "enso" -lazy val buildEngineDistribution = - taskKey[Unit]("Builds the engine distribution") -buildEngineDistribution := { +lazy val createEnginePackage = + taskKey[Unit]("Creates the engine distribution package") +createEnginePackage := { updateLibraryManifests.value val modulesToCopy = componentModulesPaths.value val root = engineDistributionRoot.value @@ -5128,12 +5127,45 @@ buildEngineDistribution := { log.info(s"Engine package created at $root") } +ThisBuild / createEnginePackage := { + createEnginePackage.result.value +} + +lazy val buildEngineDistribution = + taskKey[Unit]("Builds the engine distribution and optionally native image") +buildEngineDistribution := Def.taskIf { + if (shouldBuildNativeImage.value) { + createEnginePackage.value + (`engine-runner` / buildNativeImage).value + } else { + createEnginePackage.value + } +}.value + // This makes the buildEngineDistribution task usable as a dependency // of other tasks. ThisBuild / buildEngineDistribution := { buildEngineDistribution.result.value } +lazy val shouldBuildNativeImage = taskKey[Boolean]( + "Whether native image should be build within buildEngineDistribution task" +) + +ThisBuild / shouldBuildNativeImage := { + val prop = System.getenv("ENSO_LAUNCHER") + prop == "native" || prop == "debugnative" +} + +ThisBuild / NativeImage.additionalOpts := { + val prop = System.getenv("ENSO_LAUNCHER") + if (prop == "native") { + Seq("-O3") + } else { + Seq("-ea", "-Ob", "-H:GenerateDebugInfo=1") + } +} + ThisBuild / engineDistributionRoot := { engineDistributionRoot.value } diff --git a/build_tools/build/src/engine.rs b/build_tools/build/src/engine.rs index ef91b308a94a..d55028be10fb 100644 --- a/build_tools/build/src/engine.rs +++ b/build_tools/build/src/engine.rs @@ -174,6 +174,52 @@ impl Benchmarks { } } +/// Configuration for how the binary inside the engine distribution should be built. +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub enum EngineLauncher { + /// The binary inside the engine distribution will be built as an optimized native image + Native, + /// The binary inside the engine distribution will be built as native image with assertions + /// enabled and debug information + DebugNative, + /// The binary inside the engine distribution will be a shell script + #[default] + Shell, +} + +impl FromStr for EngineLauncher { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "native" => Ok(Self::Native), + "debugnative" => Ok(Self::DebugNative), + "shell" => Ok(Self::Shell), + _ => bail!("Invalid Engine Launcher type: {}", s), + } + } +} + +impl From for String { + fn from(value: EngineLauncher) -> Self { + match value { + EngineLauncher::Native => "native".to_string(), + EngineLauncher::DebugNative => "debugnative".to_string(), + EngineLauncher::Shell => "shell".to_string(), + } + } +} + +impl Display for EngineLauncher { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match *self { + EngineLauncher::Native => write!(f, "native"), + EngineLauncher::DebugNative => write!(f, "debugnative"), + EngineLauncher::Shell => write!(f, "shell"), + } + } +} + /// Describes what should be done with the backend. /// /// Basically a recipe of what to do with `sbt` and its artifacts. diff --git a/build_tools/build/src/engine/context.rs b/build_tools/build/src/engine/context.rs index 896783ae3e1f..ee8d6b4581ea 100644 --- a/build_tools/build/src/engine/context.rs +++ b/build_tools/build/src/engine/context.rs @@ -199,6 +199,21 @@ impl RunContext { }; sbt.call_arg("syntax-rust-definition/Runtime/managedClasspath").await?; } + if self.config.build_native_runner { + env::ENSO_LAUNCHER.set(&engine::EngineLauncher::DebugNative)?; + } + + // TODO: Once the native image is production ready, we should switch to + // EngineLauncher::Native on release versions. + // See tasks in https://github.com/orgs/enso-org/discussions/10121 + /*let version = versions_from_env()?.unwrap(); + let is_release = version.release_mode; + let kind = Kind::deduce(&version)?; + if is_release { + env::ENSO_LAUNCHER.set(&engine::EngineLauncher::Native)?; + } else { + env::ENSO_LAUNCHER.set(&engine::EngineLauncher::Shell)?; + }*/ prepare_simple_library_server.await??; @@ -333,9 +348,6 @@ impl RunContext { if self.config.build_engine_package() { tasks.push("buildEngineDistribution"); } - if self.config.build_native_runner { - tasks.push("engine-runner/buildNativeImage"); - } if self.config.build_native_ydoc { tasks.push("ydoc-server/buildNativeImage"); } diff --git a/build_tools/build/src/engine/env.rs b/build_tools/build/src/engine/env.rs index 0f42b2916e30..d7133e1c0590 100644 --- a/build_tools/build/src/engine/env.rs +++ b/build_tools/build/src/engine/env.rs @@ -1,6 +1,7 @@ //! Environment variables used by the engine's SBT-based build system. //use crate::prelude::*; +use crate::engine; use ide_ci::cache::goodie::graalvm; use ide_ci::define_env_var; @@ -16,4 +17,7 @@ define_env_var! { /// GraalVM edition. Either Community or Enterprise. GRAAL_EDITION, graalvm::Edition; + + /// Type of the launcher - either 'native', 'debugnative' or 'shell' + ENSO_LAUNCHER, engine::EngineLauncher; } diff --git a/docs/infrastructure/native-image.md b/docs/infrastructure/native-image.md index 216e69290d98..aa9d5be6fd18 100644 --- a/docs/infrastructure/native-image.md +++ b/docs/infrastructure/native-image.md @@ -201,12 +201,29 @@ safely. ### Engine runner Configuration The Native Image generation for the Engine Runner is currently in a preview -state. To generate the Native Image for runner simply execute +state. It is triggered by `ENSO_LAUNCHER` environment variable. Its value can be +one of the following: + +- `shell`: The default value. `buildEngineDistribution` command does not build + the native image. +- `debugnative`: `buildEngineDistribution` command builds native image with + assertions enabled (`-ea`). Useful for running tests on the CI. +- `native`: `buildEngineDistribution` command builds native image with + assertions disabled (`-ea`). Turns on maximal optimizations which may increase + the build time. + +To generate the Native Image for runner either explicitly execute ```bash sbt> engine-runner/buildNativeImage ``` +or + +```bash +$ ENSO_LAUNCHER=native sbt buildEngineDistribution +``` + and execute any program with that binary - for example `test/Base_Tests` ```bash diff --git a/project/NativeImage.scala b/project/NativeImage.scala index 3ffbf5ec5c77..eb0970fe1663 100644 --- a/project/NativeImage.scala +++ b/project/NativeImage.scala @@ -10,17 +10,16 @@ import scala.sys.process._ object NativeImage { - /** Specifies whether the build executable should include debug symbols. - * Should be set to false for production builds. May work only on Linux. - */ - private val includeDebugInfo: Boolean = false - lazy val smallJdk = taskKey[Option[File]]("Location of a minimal JDK") lazy val additionalCp = taskKey[Seq[String]]( "Additional class-path entries to be added to the native image" ) + lazy val additionalOpts = settingKey[Seq[String]]( + "Additional options for the native-image tool" + ) + /** List of classes that should be initialized at build time by the native image. * Note that we strive to initialize as much classes during the native image build * time as possible, as this reduces the time needed to start the native image. @@ -143,9 +142,6 @@ object NativeImage { } - val debugParameters = - if (includeDebugInfo) Seq("-H:GenerateDebugInfo=1") else Seq() - val (staticParameters, pathExts) = if (staticOnLinux && Platform.isLinux) { // Note [Static Build On Linux] @@ -169,9 +165,6 @@ object NativeImage { Seq() } - val quickBuildOption = - if (BuildInfo.isReleaseMode) Seq() else Seq("-Ob") - val buildMemoryLimitOptions = buildMemoryLimitMegabytes.map(megs => s"-J-Xmx${megs}M").toSeq @@ -202,8 +195,8 @@ object NativeImage { var args: Seq[String] = Seq("-cp", cpStr) ++ - quickBuildOption ++ - debugParameters ++ staticParameters ++ configs ++ + staticParameters ++ + configs ++ Seq("--no-fallback", "--no-server") ++ Seq("-march=compatibility") ++ initializeAtBuildtimeOptions ++ @@ -211,6 +204,7 @@ object NativeImage { buildMemoryLimitOptions ++ runtimeMemoryOptions ++ additionalOptions ++ + additionalOpts.value ++ Seq("-o", targetLoc.toString) args = mainClass match { @@ -306,7 +300,7 @@ object NativeImage { else Def.task { streams.value.log.info( - s"No source changes, $artifactName Native Image is up to date." + s"No source changes, $name Native Image is up to date." ) } }