Skip to content

Commit

Permalink
Introduce ENSO_LAUNCHER env var to configure behavior of buildEngineD…
Browse files Browse the repository at this point in the history
…istribution (#12035)

Introduce `ENSO_LAUNCHER` env var that configures if and how the native image of `engine-runner` is built (More specifically, how `sbt buildEngineDistribution` command behaves):
- `native`: NI is built optimized without assertions and without debugging symbols
- `debugnative`: NI is built not-optimized with assertions enabled and with debugging symbols.
- `shell` (the default value): NI is not built - `buildEngineDistribution` continues to behave as on develop.

Docs in [native-image.md](https://github.com/enso-org/enso/blob/e5fe54105a05fcf3235c57ff722c22a9236528f1/docs/infrastructure/native-image.md#L201-L214)
  • Loading branch information
Akirathan authored Jan 16, 2025
1 parent d2c279f commit ddda787
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 23 deletions.
42 changes: 37 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -3823,7 +3822,7 @@ lazy val `engine-runner` = project
.dependsOn(NativeImage.additionalCp)
.dependsOn(NativeImage.smallJdk)
.dependsOn(
buildEngineDistribution
createEnginePackage
)
.value,
buildNativeImage := Def.taskDyn {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down
46 changes: 46 additions & 0 deletions build_tools/build/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
match s {
"native" => Ok(Self::Native),
"debugnative" => Ok(Self::DebugNative),
"shell" => Ok(Self::Shell),
_ => bail!("Invalid Engine Launcher type: {}", s),
}
}
}

impl From<EngineLauncher> 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.
Expand Down
18 changes: 15 additions & 3 deletions build_tools/build/src/engine/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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??;

Expand Down Expand Up @@ -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");
}
Expand Down
4 changes: 4 additions & 0 deletions build_tools/build/src/engine/env.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
19 changes: 18 additions & 1 deletion docs/infrastructure/native-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 8 additions & 14 deletions project/NativeImage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand All @@ -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

Expand Down Expand Up @@ -202,15 +195,16 @@ object NativeImage {

var args: Seq[String] =
Seq("-cp", cpStr) ++
quickBuildOption ++
debugParameters ++ staticParameters ++ configs ++
staticParameters ++
configs ++
Seq("--no-fallback", "--no-server") ++
Seq("-march=compatibility") ++
initializeAtBuildtimeOptions ++
initializeAtRuntimeOptions ++
buildMemoryLimitOptions ++
runtimeMemoryOptions ++
additionalOptions ++
additionalOpts.value ++
Seq("-o", targetLoc.toString)

args = mainClass match {
Expand Down Expand Up @@ -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."
)
}
}
Expand Down

0 comments on commit ddda787

Please sign in to comment.