Skip to content

Commit 83e1830

Browse files
committed
build: harden linux runtime compatibility
1 parent bc46752 commit 83e1830

8 files changed

Lines changed: 159 additions & 30 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ concurrency:
1616
jobs:
1717
version-consistency:
1818
name: Version Consistency
19-
runs-on: ubuntu-latest
19+
runs-on: ubuntu-22.04
2020
steps:
2121
- uses: actions/checkout@v4
2222
- uses: pnpm/action-setup@v4
@@ -35,7 +35,7 @@ jobs:
3535
fail-fast: false
3636
matrix:
3737
os:
38-
- ubuntu-latest
38+
- ubuntu-22.04
3939
- macos-latest
4040
- windows-latest
4141
steps:
@@ -50,29 +50,28 @@ jobs:
5050

5151
verify-linux:
5252
name: Verify Linux Runtime
53-
runs-on: ubuntu-latest
53+
runs-on: ubuntu-22.04
5454
needs: version-consistency
5555
env:
5656
CODER_STUDIO_START_TIMEOUT_MS: 45000
57+
CODER_STUDIO_RUST_TARGET: x86_64-unknown-linux-musl
5758
steps:
5859
- uses: actions/checkout@v4
59-
- name: Install Linux runtime dependencies
60+
- name: Install Linux build dependencies
6061
run: |
6162
sudo apt-get update
6263
sudo apt-get install -y \
6364
build-essential \
6465
curl \
6566
file \
66-
libayatana-appindicator3-dev \
67-
libssl-dev \
68-
libwebkit2gtk-4.1-dev \
69-
librsvg2-dev \
70-
patchelf \
67+
musl-tools \
7168
xvfb \
7269
wget
7370
- uses: dtolnay/rust-toolchain@stable
7471
with:
7572
components: rustfmt, clippy
73+
- name: Install Linux musl target
74+
run: rustup target add x86_64-unknown-linux-musl
7675
- uses: Swatinem/rust-cache@v2
7776
with:
7877
workspaces: apps/server -> .build/server/target

.github/workflows/release.yml

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ concurrency:
1616
jobs:
1717
preflight:
1818
name: Release Preflight
19-
runs-on: ubuntu-latest
19+
runs-on: ubuntu-22.04
2020
steps:
2121
- uses: actions/checkout@v4
2222
with:
@@ -41,7 +41,7 @@ jobs:
4141
fail-fast: false
4242
matrix:
4343
include:
44-
- os: ubuntu-latest
44+
- os: ubuntu-22.04
4545
package_slug: coder-studio-linux-x64
4646
- os: macos-latest
4747
package_slug: coder-studio-darwin-arm64
@@ -52,20 +52,19 @@ jobs:
5252
steps:
5353
- uses: actions/checkout@v4
5454
- if: startsWith(matrix.os, 'ubuntu')
55-
name: Install Linux runtime dependencies
55+
name: Install Linux build dependencies
5656
run: |
5757
sudo apt-get update
5858
sudo apt-get install -y \
5959
build-essential \
6060
curl \
6161
file \
62-
libayatana-appindicator3-dev \
63-
libssl-dev \
64-
libwebkit2gtk-4.1-dev \
65-
librsvg2-dev \
66-
patchelf \
62+
musl-tools \
6763
wget
6864
- uses: dtolnay/rust-toolchain@stable
65+
- if: startsWith(matrix.os, 'ubuntu')
66+
name: Install Linux musl target
67+
run: rustup target add x86_64-unknown-linux-musl
6968
- uses: Swatinem/rust-cache@v2
7069
with:
7170
workspaces: apps/server -> .build/server/target
@@ -75,6 +74,9 @@ jobs:
7574
node-version: 22
7675
cache: pnpm
7776
registry-url: 'https://registry.npmjs.org'
77+
- if: startsWith(matrix.os, 'ubuntu')
78+
name: Use Linux musl release target
79+
run: echo "CODER_STUDIO_RUST_TARGET=x86_64-unknown-linux-musl" >> "$GITHUB_ENV"
7880
- run: pnpm install --no-frozen-lockfile
7981
- run: pnpm release:check
8082
- run: 'node -e "require(''fs'').mkdirSync(''.artifacts'', { recursive: true })"'
@@ -94,28 +96,32 @@ jobs:
9496
needs:
9597
- preflight
9698
- publish-platform
97-
runs-on: ubuntu-latest
99+
runs-on: ubuntu-22.04
98100
steps:
99101
- uses: actions/checkout@v4
100-
- name: Install Linux runtime dependencies
102+
- name: Install Linux build dependencies
101103
run: |
102104
sudo apt-get update
103105
sudo apt-get install -y \
104106
build-essential \
105107
curl \
106108
file \
107-
libayatana-appindicator3-dev \
108-
libssl-dev \
109-
libwebkit2gtk-4.1-dev \
110-
librsvg2-dev \
111-
patchelf \
109+
musl-tools \
112110
wget
111+
- uses: dtolnay/rust-toolchain@stable
112+
- name: Install Linux musl target
113+
run: rustup target add x86_64-unknown-linux-musl
114+
- uses: Swatinem/rust-cache@v2
115+
with:
116+
workspaces: apps/server -> .build/server/target
113117
- uses: pnpm/action-setup@v4
114118
- uses: actions/setup-node@v4
115119
with:
116120
node-version: 22
117121
cache: pnpm
118122
registry-url: 'https://registry.npmjs.org'
123+
- name: Use Linux musl release target
124+
run: echo "CODER_STUDIO_RUST_TARGET=x86_64-unknown-linux-musl" >> "$GITHUB_ENV"
119125
- run: pnpm install --no-frozen-lockfile
120126
- run: pnpm release:check
121127
- run: 'node -e "require(''fs'').mkdirSync(''.artifacts'', { recursive: true })"'
@@ -135,7 +141,7 @@ jobs:
135141
needs:
136142
- publish-platform
137143
- publish-main
138-
runs-on: ubuntu-latest
144+
runs-on: ubuntu-22.04
139145
steps:
140146
- uses: actions/checkout@v4
141147
- uses: pnpm/action-setup@v4

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"dev:frontend": "vite",
1212
"build": "pnpm build:web",
1313
"build:web": "vite build",
14-
"build:server": "cargo build --release --manifest-path apps/server/Cargo.toml",
14+
"build:server": "node scripts/build/build-server.mjs",
15+
"build:server:linux-musl": "node scripts/build/build-server.mjs --target x86_64-unknown-linux-musl",
1516
"build:runtime": "pnpm build:server",
1617
"build:cli": "node scripts/build/build-cli.mjs",
1718
"check:server": "cargo check --manifest-path apps/server/Cargo.toml",

scripts/build/build-packages.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import {
44
MAIN_PACKAGE,
55
NPM_STAGE_ROOT,
66
resolvePlatformPackageMeta,
7-
SERVER_TARGET_DIR,
87
WEB_DIST_DIR,
98
} from '../lib/package-matrix.mjs';
9+
import { resolveServerBinaryPath } from '../lib/server-build.mjs';
1010

1111
const packageMeta = resolvePlatformPackageMeta();
1212
if (!packageMeta) {
1313
throw new Error(`Unsupported platform for package assembly: ${process.platform}/${process.arch}`);
1414
}
1515

1616
const binaryName = process.platform === 'win32' ? 'coder-studio.exe' : 'coder-studio';
17-
const binarySource = path.join(SERVER_TARGET_DIR, 'release', binaryName);
17+
const binarySource = resolveServerBinaryPath();
1818
const distSource = WEB_DIST_DIR;
1919
const packageRoot = packageMeta.stageDir;
2020
const binaryTarget = path.join(packageRoot, 'bin', binaryName);

scripts/build/build-server.mjs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { spawn } from 'node:child_process';
2+
import { ROOT } from '../lib/package-matrix.mjs';
3+
import {
4+
buildServerCargoArgs,
5+
resolveRustTarget,
6+
resolveServerBinaryPath,
7+
} from '../lib/server-build.mjs';
8+
9+
const cliArgs = process.argv.slice(2);
10+
const targetIndex = cliArgs.indexOf('--target');
11+
12+
if (targetIndex !== -1) {
13+
const target = cliArgs[targetIndex + 1];
14+
if (!target) {
15+
throw new Error('missing value for --target');
16+
}
17+
process.env.CODER_STUDIO_RUST_TARGET = target;
18+
cliArgs.splice(targetIndex, 2);
19+
}
20+
21+
if (cliArgs.length > 0) {
22+
throw new Error(`unsupported arguments: ${cliArgs.join(' ')}`);
23+
}
24+
25+
const cargoArgs = buildServerCargoArgs({ env: process.env });
26+
await new Promise((resolve, reject) => {
27+
const child = spawn('cargo', cargoArgs, {
28+
cwd: ROOT,
29+
env: process.env,
30+
stdio: 'inherit',
31+
});
32+
33+
child.on('error', reject);
34+
child.on('exit', (code, signal) => {
35+
if (signal) {
36+
reject(new Error(`cargo build terminated by signal ${signal}`));
37+
return;
38+
}
39+
if (code === 0) {
40+
resolve();
41+
return;
42+
}
43+
reject(new Error(`cargo build failed with exit code ${code ?? 'unknown'}`));
44+
});
45+
});
46+
47+
const rustTarget = resolveRustTarget({ env: process.env });
48+
console.log(`built coder-studio server${rustTarget ? ` (${rustTarget})` : ''}`);
49+
console.log(`binary: ${resolveServerBinaryPath({ env: process.env })}`);

scripts/lib/server-build.mjs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import path from 'node:path';
2+
import { ROOT, SERVER_APP_DIR, SERVER_TARGET_DIR } from './package-matrix.mjs';
3+
4+
export function resolveRustTarget({ env = process.env } = {}) {
5+
const value = env.CODER_STUDIO_RUST_TARGET;
6+
if (typeof value !== 'string') return '';
7+
return value.trim();
8+
}
9+
10+
export function resolveServerBinaryName(platform = process.platform) {
11+
return platform === 'win32' ? 'coder-studio.exe' : 'coder-studio';
12+
}
13+
14+
export function resolveServerBinaryPath({
15+
env = process.env,
16+
platform = process.platform,
17+
profile = 'release',
18+
} = {}) {
19+
const binaryName = resolveServerBinaryName(platform);
20+
const rustTarget = resolveRustTarget({ env });
21+
if (rustTarget) {
22+
return path.join(SERVER_TARGET_DIR, rustTarget, profile, binaryName);
23+
}
24+
return path.join(SERVER_TARGET_DIR, profile, binaryName);
25+
}
26+
27+
export function buildServerCargoArgs({
28+
env = process.env,
29+
profile = 'release',
30+
manifestPath = path.join(SERVER_APP_DIR, 'Cargo.toml'),
31+
} = {}) {
32+
const args = ['build'];
33+
if (profile === 'release') {
34+
args.push('--release');
35+
} else {
36+
args.push('--profile', profile);
37+
}
38+
args.push('--manifest-path', path.relative(ROOT, manifestPath));
39+
40+
const rustTarget = resolveRustTarget({ env });
41+
if (rustTarget) {
42+
args.push('--target', rustTarget);
43+
}
44+
45+
return args;
46+
}

scripts/test/start-release-server.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import fs from 'node:fs/promises';
22
import path from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { startRuntime, stopRuntime } from '../../.build/cli/lib/runtime-controller.mjs';
5+
import { resolveServerBinaryPath } from '../lib/server-build.mjs';
56

67
const ROOT = fileURLToPath(new URL('../..', import.meta.url));
7-
const binaryName = process.platform === 'win32' ? 'coder-studio.exe' : 'coder-studio';
8-
const binaryPath = path.join(ROOT, '.build', 'server', 'target', 'release', binaryName);
8+
const binaryPath = resolveServerBinaryPath();
99
const distDir = path.join(ROOT, '.build', 'web', 'dist');
1010
const stateDir = path.join(ROOT, '.tmp', 'release-e2e-runtime');
1111
const env = {

tests/release/release.test.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import fs from 'node:fs/promises';
44
import os from 'node:os';
55
import path from 'node:path';
66
import { ROOT } from '../../scripts/lib/package-matrix.mjs';
7+
import {
8+
buildServerCargoArgs,
9+
resolveServerBinaryPath,
10+
} from '../../scripts/lib/server-build.mjs';
711
import { assertReleaseAssets } from '../../scripts/release/check-assets.mjs';
812
import { assertVersionConsistency, collectReleaseVersionState } from '../../scripts/release/check-version.mjs';
913
import { createReleaseManifest } from '../../scripts/release/write-release-manifest.mjs';
@@ -21,6 +25,30 @@ test('release versions stay aligned across package manifests', async () => {
2125
assert.equal(state.mainVersion, state.cargoVersion);
2226
});
2327

28+
test('server build helpers default to the native release output path', () => {
29+
const env = {};
30+
assert.equal(
31+
resolveServerBinaryPath({ env, platform: 'linux' }),
32+
path.join(ROOT, '.build', 'server', 'target', 'release', 'coder-studio'),
33+
);
34+
assert.deepEqual(
35+
buildServerCargoArgs({ env }),
36+
['build', '--release', '--manifest-path', path.join('apps', 'server', 'Cargo.toml')],
37+
);
38+
});
39+
40+
test('server build helpers route Linux musl builds into the target-specific output path', () => {
41+
const env = { CODER_STUDIO_RUST_TARGET: 'x86_64-unknown-linux-musl' };
42+
assert.equal(
43+
resolveServerBinaryPath({ env, platform: 'linux' }),
44+
path.join(ROOT, '.build', 'server', 'target', 'x86_64-unknown-linux-musl', 'release', 'coder-studio'),
45+
);
46+
assert.deepEqual(
47+
buildServerCargoArgs({ env }),
48+
['build', '--release', '--manifest-path', path.join('apps', 'server', 'Cargo.toml'), '--target', 'x86_64-unknown-linux-musl'],
49+
);
50+
});
51+
2452
test('release manifest writer emits checksums for tarballs', async () => {
2553
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'coder-studio-release-'));
2654

0 commit comments

Comments
 (0)