diff --git a/core/src/commands/self-update.ts b/core/src/commands/self-update.ts index 1585c4752a..2b00424d03 100644 --- a/core/src/commands/self-update.ts +++ b/core/src/commands/self-update.ts @@ -47,7 +47,7 @@ const selfUpdateOpts = { help: `Specify an installation directory, instead of using the directory of the Garden CLI being used. Implies --force.`, }), "platform": new ChoicesParameter({ - choices: ["macos", "linux", "windows"], + choices: ["macos", "linux", "alpine", "windows"], help: `Override the platform, instead of detecting it automatically.`, }), "architecture": new ChoicesParameter({ @@ -332,7 +332,11 @@ export class SelfUpdateCommand extends Command { try { if (!platform) { - platform = getPlatform() === "darwin" ? "macos" : getPlatform() + platform = getPlatform() + + if (platform === "darwin") { + platform = "macos" + } } let architecture: Architecture = opts.architecture ? (opts.architecture as Architecture) : getArchitecture() diff --git a/core/src/plugin/tools.ts b/core/src/plugin/tools.ts index 335577ebdf..b7d49ac603 100644 --- a/core/src/plugin/tools.ts +++ b/core/src/plugin/tools.ts @@ -26,7 +26,7 @@ const toolBuildSchema = createSchema({ keys: () => ({ platform: joi .string() - .allow("darwin", "linux", "windows") + .allow("darwin", "linux", "alpine", "windows") .required() .example("linux") .description("The platform this build is for."), diff --git a/core/src/util/arch-platform.ts b/core/src/util/arch-platform.ts index 0afa97f2ca..5239f04e71 100644 --- a/core/src/util/arch-platform.ts +++ b/core/src/util/arch-platform.ts @@ -8,21 +8,50 @@ import { memoize } from "lodash-es" import { execSync } from "child_process" +import { InternalError } from "../exceptions.js" -const platformMap = { - win32: "windows" as const, -} const archMap = { x32: "386" as const, x64: "amd64" as const, -} -export type Architecture = Exclude | (typeof archMap)[keyof typeof archMap] -export type Platform = - | Exclude - | (typeof platformMap)[keyof typeof platformMap] +} as const +const supportedArchitectures = ["386", "amd64", "arm64"] as const +const supportedPlatforms = ["darwin", "windows", "linux", "alpine"] as const +export type Platform = (typeof supportedPlatforms)[number] +export type Architecture = (typeof supportedArchitectures)[number] export function getPlatform(): Platform { - return platformMap[process.platform] || process.platform + const platform = process.platform + + if (platform === "win32") { + return "windows" + } + + if (platform === "linux") { + if (getRustTargetEnv() === "musl") { + return "alpine" + } + + return "linux" + } + + if (platform === "darwin") { + return "darwin" + } + + throw new InternalError({ message: `Unsupported platform: ${platform}` }) +} + +// rust target env +// The Garden SEA rust wrapper will set an environment variable called GARDEN_SEA_TARGET_ENV on linux so we can download Alpine binaries if needed. +type RustTargetEnv = undefined | "musl" | "gnu" +export function getRustTargetEnv(): RustTargetEnv { + const targetEnv = process.env.GARDEN_SEA_TARGET_ENV + + if (targetEnv === undefined || targetEnv === "musl" || targetEnv === "gnu") { + return targetEnv + } + + throw new InternalError({ message: `Invalid value for GARDEN_SEA_TARGET_ENV: ${targetEnv}` }) } export function getArchitecture(): Architecture { @@ -30,8 +59,13 @@ export function getArchitecture(): Architecture { // process.arch is always x64 even though the underlying CPU architecture may be arm64 // To check if we are running under Rosetta, // use the `isDarwinARM` function below - const arch = process.arch - return archMap[arch] || arch + const arch = archMap[process.arch] || process.arch + + if (!supportedArchitectures.includes(arch)) { + throw new InternalError({ message: `Unsupported architecture: ${arch}` }) + } + + return arch } export const isDarwinARM = memoize(() => { diff --git a/core/src/util/ext-tools.ts b/core/src/util/ext-tools.ts index bc26fd4346..c7c0e49f14 100644 --- a/core/src/util/ext-tools.ts +++ b/core/src/util/ext-tools.ts @@ -216,6 +216,8 @@ export class PluginTool extends CliWrapper { if (darwinARM) { // first look for native arch, if not found, then try (potentially emulated) arch buildSpec = findBuildSpec(spec, platform, "arm64") || findBuildSpec(spec, platform, "amd64") + } else if (platform === "alpine") { + buildSpec = findBuildSpec(spec, "alpine", architecture) || findBuildSpec(spec, "linux", architecture) } else { buildSpec = findBuildSpec(spec, platform, architecture)! } diff --git a/docs/reference/commands.md b/docs/reference/commands.md index a8d3f7a6cc..dfd31bcefb 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -4938,7 +4938,7 @@ Examples: | -------- | ----- | ---- | ----------- | | `--force` | | boolean | Install the Garden CLI even if the specified or detected latest version is the same as the current version. | `--install-dir` | | string | Specify an installation directory, instead of using the directory of the Garden CLI being used. Implies --force. - | `--platform` | | `macos` `linux` `windows` | Override the platform, instead of detecting it automatically. + | `--platform` | | `macos` `linux` `alpine` `windows` | Override the platform, instead of detecting it automatically. | `--architecture` | | `arm64` `amd64` | Override the architecture, instead of detecting it automatically. | `--major` | | boolean | Install the latest major version of Garden. Falls back to the current version if the greater major version does not exist. diff --git a/garden-sea/src/artifacts.rs b/garden-sea/src/artifacts.rs index 7d8a62fb15..91261dae30 100644 --- a/garden-sea/src/artifacts.rs +++ b/garden-sea/src/artifacts.rs @@ -5,6 +5,13 @@ pub struct GardenArtifact { pub sha256: &'static [u8], } +// needed to tell the self-updater to download alpine binaries on linux +#[cfg(all(target_os = "linux", target_env = "musl"))] +pub static TARGET_ENV: &str = "musl"; + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub static TARGET_ENV: &str = "gnu"; + // source pub static SOURCE: GardenArtifact = GardenArtifact { diff --git a/garden-sea/src/node.rs b/garden-sea/src/node.rs index bb37c13e44..7d11697ceb 100644 --- a/garden-sea/src/node.rs +++ b/garden-sea/src/node.rs @@ -11,6 +11,9 @@ use eyre::{Result, WrapErr}; use crate::{log::debug, signal}; +#[cfg(all(target_os = "linux"))] +use crate::artifacts::TARGET_ENV; + pub(crate) fn spawn_garden(path: &Path, sea_args: T) -> Result where T: Iterator, @@ -31,12 +34,20 @@ where command.env("GARDEN_SEA_EXTRACTED_ROOT", OsString::from(path)); command.env("GARDEN_SEA_EXECUTABLE_PATH", executable_path); + // exposes GARDEN_SEA_TARGET_ENV variable to self-update command, so it can decide to download alpine + // binaries on linux. + #[cfg(all(target_os = "linux"))] + command.env("GARDEN_SEA_TARGET_ENV", TARGET_ENV); + // Libuv 1.45.0 is affected by a kernel bug on certain kernels (Ubuntu 22) // This leads to errors where Garden tool downloading errors with ETXTBSY // Apparently file descriptor accounting is broken when using USE_IO_URING on older kernels // See also: https://github.com/libuv/libuv/pull/4141/files // TODO: Remove this once libuv 1.47 landed in a future NodeJS version, and we upgraded to it. - command.env("UV_USE_IO_URING", env::var("GARDEN_SEA_UV_USE_IO_URING").unwrap_or("0".into())); + command.env( + "UV_USE_IO_URING", + env::var("GARDEN_SEA_UV_USE_IO_URING").unwrap_or("0".into()), + ); // Allow users to override the heap size if needed. let max_old_space_size = env::var("GARDEN_MAX_OLD_SPACE_SIZE").unwrap_or("4096".into());