Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ENSO_LAUNCHER env var to configure behavior of buildEngineDistribution #12035

Merged
merged 15 commits into from
Jan 16, 2025
Merged
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
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also check line 283:

Espresso support works also with
[native image support](#engine-runner-configuration). Just make sure
`ENSO_JAVA=espresso` is specified when building the `runner` executable:

```bash
enso$ rm ./built-distribution/enso-engine-*/enso-*/bin/enso
enso$ ENSO_JAVA=espresso sbt --java-home /graalvm
sbt> engine-runner/buildNativeImage

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 ++
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionalOptions.. additionalOpts.. additionalFoo. This is getting confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit. One is a parameter to the method, and the other one is a value from the task. I don't know now how to merge these two, with minimal changes.

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
Loading