diff --git a/editors/vscode/scripts/package/server.ts b/editors/vscode/scripts/package/server.ts index 3908d884..ff4dd994 100644 --- a/editors/vscode/scripts/package/server.ts +++ b/editors/vscode/scripts/package/server.ts @@ -5,6 +5,11 @@ import type { PackageContext } from './context'; import { run } from './process'; import type { BuildProfile, NativeTargetSpec, ServerMode } from './targets'; +const ALPINE_CARGO_TARGETS: Partial> = { + 'alpine-arm64': 'aarch64-unknown-linux-musl', + 'alpine-x64': 'x86_64-unknown-linux-musl', +}; + export function ensureTargetServerBinary( context: PackageContext, spec: NativeTargetSpec, @@ -28,7 +33,7 @@ export function ensureTargetServerBinary( } const serverPath = targetServerPath(context, spec); - run('cargo', args, context.repoRoot); + run('cargo', args, context.repoRoot, cargoXtaskEnvForTarget(spec)); if (!fs.existsSync(serverPath)) { throw new Error(`prepared server binary was not found: ${serverPath}`); @@ -63,3 +68,70 @@ export function cleanRuntimeServerFiles(context: PackageContext): void { function targetServerPath(context: PackageContext, spec: NativeTargetSpec): string { return path.join(context.vscodeDir, 'server', spec.target, spec.binaryFile); } + +export function cargoXtaskEnvForTarget( + spec: NativeTargetSpec, + env: NodeJS.ProcessEnv = process.env, +): NodeJS.ProcessEnv { + const cargoTarget = ALPINE_CARGO_TARGETS[spec.target]; + if (!cargoTarget) { + return env; + } + + let updated = envWithoutCargoBuildTarget(env); + const linkerEnvKey = cargoTargetLinkerEnvKey(cargoTarget); + if (!optionalEnv(updated, linkerEnvKey)) { + updated = { + ...updated, + [linkerEnvKey]: cargoLinkerForTarget(cargoTarget, updated), + }; + } + + return appendRustFlags(updated, ['-C', 'link-arg=-lc']); +} + +function envWithoutCargoBuildTarget(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const { CARGO_BUILD_TARGET: _cargoBuildTarget, ...updated } = env; + return updated; +} + +function cargoTargetLinkerEnvKey(cargoTarget: string): string { + return `CARGO_TARGET_${cargoTargetEnvName(cargoTarget)}_LINKER`; +} + +function cargoTargetEnvName(cargoTarget: string): string { + return cargoTarget.toUpperCase().replace(/-/g, '_'); +} + +function cargoLinkerForTarget(cargoTarget: string, env: NodeJS.ProcessEnv): string { + return ( + optionalEnv(env, cxxCompilerEnvKey(cargoTarget)) ?? + optionalEnv(env, 'TARGET_CXX') ?? + `${cargoTarget}-g++` + ); +} + +function cxxCompilerEnvKey(cargoTarget: string): string { + return `CXX_${cargoTarget.replace(/-/g, '_')}`; +} + +function appendRustFlags(env: NodeJS.ProcessEnv, flags: string[]): NodeJS.ProcessEnv { + const encodedFlags = optionalEnv(env, 'CARGO_ENCODED_RUSTFLAGS'); + if (encodedFlags) { + return { + ...env, + CARGO_ENCODED_RUSTFLAGS: `${encodedFlags}\x1f${flags.join('\x1f')}`, + }; + } + + const rustFlags = optionalEnv(env, 'RUSTFLAGS'); + return { + ...env, + RUSTFLAGS: rustFlags ? `${rustFlags} ${flags.join(' ')}` : flags.join(' '), + }; +} + +function optionalEnv(env: NodeJS.ProcessEnv, name: string): string | undefined { + const value = env[name]?.trim(); + return value ? value : undefined; +} diff --git a/editors/vscode/test/package.test.ts b/editors/vscode/test/package.test.ts index 0f5fce05..00bf65d6 100644 --- a/editors/vscode/test/package.test.ts +++ b/editors/vscode/test/package.test.ts @@ -11,7 +11,8 @@ import { stagePackageJsonForTarget, stageProfileTraceAssets, } from '../scripts/package/manifest'; -import { createPackagePlan } from '../scripts/package/targets'; +import { cargoXtaskEnvForTarget } from '../scripts/package/server'; +import { createPackagePlan, type NativeTargetSpec } from '../scripts/package/targets'; describe('package cli', () => { it('keeps the existing debug positional target syntax', () => { @@ -105,6 +106,43 @@ describe('package staging', () => { }); }); +describe('package server preparation', () => { + it('removes inherited Cargo build targets before cargo xtask is built', () => { + const baseEnv = { CARGO_BUILD_TARGET: 'x86_64-unknown-linux-musl' }; + const env = cargoXtaskEnvForTarget(nativeTargetSpec('alpine-x64'), baseEnv); + + assert.equal(env.CARGO_BUILD_TARGET, undefined); + assert.equal( + env.CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER, + 'x86_64-unknown-linux-musl-g++', + ); + assert.equal(env.RUSTFLAGS, '-C link-arg=-lc'); + assert.equal(baseEnv.CARGO_BUILD_TARGET, 'x86_64-unknown-linux-musl'); + }); + + it('appends Alpine cargo flags to encoded rust flags', () => { + const env = cargoXtaskEnvForTarget(nativeTargetSpec('alpine-arm64'), { + CARGO_ENCODED_RUSTFLAGS: '-C\x1ftarget-feature=+crt-static', + CXX_aarch64_unknown_linux_musl: 'custom-aarch64-g++', + }); + + assert.equal(env.CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER, 'custom-aarch64-g++'); + assert.equal( + env.CARGO_ENCODED_RUSTFLAGS, + '-C\x1ftarget-feature=+crt-static\x1f-C\x1flink-arg=-lc', + ); + }); + + it('leaves non-Alpine cargo env untouched', () => { + const baseEnv = { RUSTFLAGS: '-Dwarnings' }; + + assert.equal( + cargoXtaskEnvForTarget(nativeTargetSpec('win32-x64', 'vide.exe'), baseEnv), + baseEnv, + ); + }); +}); + function temporaryPackageContext(): PackageContext { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'vide-package-')); return { @@ -113,6 +151,19 @@ function temporaryPackageContext(): PackageContext { }; } +function nativeTargetSpec( + target: NativeTargetSpec['target'], + binaryFile = 'vide', +): NativeTargetSpec { + return { + kind: 'native', + target, + binaryFile, + isWindows: target.startsWith('win32-'), + removeBrowserEntry: true, + }; +} + describe('package plan', () => { it('models web packages without native server staging', () => { const plan = createPackagePlan({ diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 42313ace..97518e3a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -417,7 +417,7 @@ fn run_command( cwd: &Path, env_updates: &[(String, String)], ) -> Result<()> { - let mut child = ProcessCommand::new(command_for_host(command)); + let mut child = ProcessCommand::new(command); child.args(args).current_dir(cwd); for (key, value) in env_updates { child.env(key, value); @@ -434,10 +434,6 @@ fn run_command( Ok(()) } -fn command_for_host(command: &str) -> String { - if cfg!(windows) { format!("{command}.cmd") } else { command.to_owned() } -} - fn workspace_root() -> Result { Path::new(env!("CARGO_MANIFEST_DIR")) .parent()