Skip to content

Commit 2bb01bf

Browse files
committed
feat: prebuild libuv riscv64 suite
1 parent 005615c commit 2bb01bf

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

.github/workflows/build-artifact.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ jobs:
7171
sudo apt-get update
7272
sudo apt-get install -y binutils-riscv64-linux-gnu xz-utils zstd
7373
74+
- name: Install libuv cross-build tools
75+
if: inputs.artifact_id == 'libuv-suite-1.51.0'
76+
run: sudo apt-get install -y cmake gcc-riscv64-linux-gnu ninja-build
77+
7478
- name: Build artifact
7579
run: |
7680
node scripts/build-artifact.mjs \
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { spawnSync } from "node:child_process";
2+
import { createHash } from "node:crypto";
3+
import {
4+
copyFileSync,
5+
cpSync,
6+
existsSync,
7+
lstatSync,
8+
mkdirSync,
9+
readdirSync,
10+
readFileSync,
11+
rmSync,
12+
statSync,
13+
writeFileSync,
14+
} from "node:fs";
15+
import { basename, dirname, join, relative, resolve } from "node:path";
16+
import {
17+
BUILDER_VERSION,
18+
RELEASE_SCHEMA_VERSION,
19+
} from "../../scripts/release-contract.mjs";
20+
21+
const VERSION = "1.51.0";
22+
const SOURCE = {
23+
url: "https://github.com/libuv/libuv/archive/refs/tags/v1.51.0.tar.gz",
24+
fileName: "libuv-1.51.0.tar.gz",
25+
sha256: "27e55cf7083913bfb6826ca78cde9de7647cded648d35f24163f2d31bb9f51cd",
26+
};
27+
const PAYLOAD_ROOT = "opt/tidemark/libuv";
28+
const EXECUTABLE = `${PAYLOAD_ROOT}/bin/uv_run_tests_a`;
29+
30+
function writeJson(path, value) {
31+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
32+
}
33+
34+
function sha256File(path) {
35+
const hash = createHash("sha256");
36+
hash.update(readFileSync(path));
37+
return hash.digest("hex");
38+
}
39+
40+
function run(command, args, options = {}) {
41+
const result = spawnSync(command, args, {
42+
encoding: "buffer",
43+
maxBuffer: 512 * 1024 * 1024,
44+
stdio: "inherit",
45+
...options,
46+
});
47+
if (result.status !== 0) {
48+
throw new Error(`${command} ${args.join(" ")} failed with ${result.status}`);
49+
}
50+
}
51+
52+
async function downloadFile(url, path) {
53+
const response = await fetch(url);
54+
if (!response.ok) {
55+
throw new Error(`failed to download ${url}: ${response.status}`);
56+
}
57+
writeFileSync(path, Buffer.from(await response.arrayBuffer()));
58+
}
59+
60+
function listFiles(root) {
61+
const result = [];
62+
const visit = (dir) => {
63+
for (const name of readdirSync(dir)) {
64+
const path = join(dir, name);
65+
const stat = lstatSync(path);
66+
if (stat.isDirectory()) {
67+
visit(path);
68+
} else if (stat.isFile()) {
69+
result.push(path);
70+
}
71+
}
72+
};
73+
if (existsSync(root)) {
74+
visit(root);
75+
}
76+
return result.sort();
77+
}
78+
79+
function tarZstd(sourceDir, outPath) {
80+
run("tar", ["--zstd", "-cf", outPath, "-C", sourceDir, "."]);
81+
}
82+
83+
export async function build({ artifactId, recipeDir, outDir }) {
84+
const workDir = resolve(outDir, ".work");
85+
const sourceDir = resolve(workDir, "sources");
86+
const extractDir = resolve(workDir, "extract");
87+
const buildDir = resolve(workDir, "build");
88+
const payloadDir = resolve(workDir, "payload");
89+
const licensesDir = resolve(workDir, "licenses");
90+
const sourcePath = resolve(sourceDir, SOURCE.fileName);
91+
const sourceRoot = resolve(extractDir, `libuv-${VERSION}`);
92+
const payloadRoot = resolve(payloadDir, PAYLOAD_ROOT);
93+
const executablePath = resolve(payloadDir, EXECUTABLE);
94+
95+
rmSync(workDir, { recursive: true, force: true });
96+
mkdirSync(sourceDir, { recursive: true });
97+
mkdirSync(extractDir, { recursive: true });
98+
mkdirSync(buildDir, { recursive: true });
99+
mkdirSync(payloadRoot, { recursive: true });
100+
mkdirSync(licensesDir, { recursive: true });
101+
102+
await downloadFile(SOURCE.url, sourcePath);
103+
const sourceSha256 = sha256File(sourcePath);
104+
if (sourceSha256 !== SOURCE.sha256) {
105+
throw new Error(`sha256 mismatch: expected ${SOURCE.sha256}, got ${sourceSha256}`);
106+
}
107+
108+
run("tar", ["-xf", sourcePath, "-C", extractDir]);
109+
run("cmake", [
110+
"-S", sourceRoot,
111+
"-B", buildDir,
112+
"-G", "Ninja",
113+
"-DBUILD_TESTING=ON",
114+
"-DCMAKE_BUILD_TYPE=Release",
115+
"-DCMAKE_SYSTEM_NAME=Linux",
116+
"-DCMAKE_SYSTEM_PROCESSOR=riscv64",
117+
"-DCMAKE_C_COMPILER=riscv64-linux-gnu-gcc",
118+
]);
119+
run("cmake", ["--build", buildDir, "--target", "uv_run_tests_a"]);
120+
121+
cpSync(sourceRoot, payloadRoot, { recursive: true });
122+
mkdirSync(dirname(executablePath), { recursive: true });
123+
copyFileSync(resolve(buildDir, "uv_run_tests_a"), executablePath);
124+
run("riscv64-linux-gnu-strip", ["--strip-debug", executablePath]);
125+
126+
const generatedAt = new Date().toISOString();
127+
const licenseFiles = ["LICENSE", "LICENSE-docs", "LICENSE-extra"].map((name) => {
128+
const source = resolve(sourceRoot, name);
129+
const target = resolve(licensesDir, name);
130+
copyFileSync(source, target);
131+
return { path: name, sha256: sha256File(target) };
132+
});
133+
writeJson(resolve(licensesDir, "LICENSE-MANIFEST.json"), {
134+
schemaVersion: RELEASE_SCHEMA_VERSION,
135+
artifactId,
136+
generatedAt,
137+
files: licenseFiles,
138+
});
139+
140+
writeJson(resolve(outDir, "manifest.json"), {
141+
schemaVersion: RELEASE_SCHEMA_VERSION,
142+
artifactId,
143+
artifactType: "upstream-suite",
144+
version: VERSION,
145+
summary: "libuv upstream source and prebuilt riscv64 test suite.",
146+
payloadRoot: PAYLOAD_ROOT,
147+
payload: {
148+
sourceRoot: PAYLOAD_ROOT,
149+
executable: EXECUTABLE,
150+
},
151+
});
152+
writeJson(resolve(outDir, "source-manifest.json"), {
153+
schemaVersion: RELEASE_SCHEMA_VERSION,
154+
artifactId,
155+
generatedAt,
156+
sources: [
157+
{ ...SOURCE, sha256: sourceSha256 },
158+
{ path: "build.mjs", sha256: sha256File(resolve(recipeDir, "build.mjs")) },
159+
],
160+
});
161+
writeJson(resolve(outDir, "build-info.json"), {
162+
schemaVersion: RELEASE_SCHEMA_VERSION,
163+
artifactId,
164+
builderVersion: BUILDER_VERSION,
165+
generatedAt,
166+
runner: {
167+
node: process.version,
168+
compiler: spawnSync("riscv64-linux-gnu-gcc", ["--version"], { encoding: "utf8" }).stdout.split("\n")[0],
169+
githubRunId: process.env.GITHUB_RUN_ID ?? null,
170+
githubSha: process.env.GITHUB_SHA ?? null,
171+
githubRef: process.env.GITHUB_REF ?? null,
172+
},
173+
});
174+
175+
tarZstd(payloadDir, resolve(outDir, "payload.tar.zst"));
176+
tarZstd(licensesDir, resolve(outDir, "licenses.tar.zst"));
177+
178+
const releaseFiles = listFiles(outDir)
179+
.filter((path) => !path.includes(`${workDir}/`))
180+
.filter((path) => basename(path) !== "sha256sums.txt");
181+
const sums = releaseFiles
182+
.map((path) => `${sha256File(path)} ${relative(outDir, path)}`)
183+
.join("\n");
184+
writeFileSync(resolve(outDir, "sha256sums.txt"), `${sums}\n`);
185+
186+
if (!statSync(executablePath).isFile()) {
187+
throw new Error(`missing built executable: ${executablePath}`);
188+
}
189+
}

0 commit comments

Comments
 (0)