1import * as NodeChildProcess from "node:child_process";
2import * as NodeFS from "node:fs";
3import * as NodePath from "node:path";
4import * as NodeURL from "node:url";
5import * as NodeUtil from "node:util";
6
7// oxlint-disable-next-line t3code/no-global-process-runtime -- The native compiler targets the actual host; this script has no Effect runtime.
8const hostArch = process.arch;
9// oxlint-disable-next-line t3code/no-global-process-runtime -- Native compilation only runs on the actual Linux host.
10const hostPlatform = process.platform;
11
12const { values } = NodeUtil.parseArgs({
13 options: { output: { type: "string" }, arch: { type: "string", default: hostArch } },
14});
15
16if (hostPlatform === "linux") {
17 const machine = { x64: 62, arm64: 183 }[values.arch];
18 if (machine === undefined) throw new Error(`Unsupported Linux architecture: ${values.arch}`);
19 const root = NodeURL.fileURLToPath(new URL("../../../native/browser-secret/", import.meta.url));
20 const source = NodePath.resolve(root, "main.c");
21 const output = values.output ?? NodePath.resolve(root, "build", values.arch, "t3-browser-secret");
22 const matchesArchitecture = (file) => {
23 const header = NodeFS.readFileSync(file).subarray(0, 20);
24 return header.toString("hex", 0, 6) === "7f454c460201" && header.readUInt16LE(18) === machine;
25 };
26 let current = false;
27 try {
28 current =
29 NodeFS.statSync(output).mtimeMs >=
30 Math.max(
31 NodeFS.statSync(source).mtimeMs,
32 NodeFS.statSync(NodeURL.fileURLToPath(import.meta.url)).mtimeMs,
33 ) && matchesArchitecture(output);
34 } catch {
35 /* The first build has no output yet. */
36 }
37 if (!current) {
38 let flags;
39 try {
40 flags = NodeChildProcess.execFileSync("pkg-config", ["--cflags", "--libs", "libsecret-1"], {
41 encoding: "utf8",
42 })
43 .trim()
44 .split(/\s+/);
45 } catch (cause) {
46 throw new Error(
47 "Building the Linux browser import helper requires pkg-config and libsecret development headers (Ubuntu/Debian: libsecret-1-dev).",
48 { cause },
49 );
50 }
51 NodeFS.mkdirSync(NodePath.dirname(output), { recursive: true });
52 const temporary = `${output}.${process.pid}.tmp`;
53 try {
54 NodeChildProcess.execFileSync(
55 process.env.CC || "cc",
56 ["-std=c11", "-O2", "-Wall", "-Wextra", "-Werror", source, "-o", temporary, ...flags],
57 { stdio: "inherit" },
58 );
59 if (!matchesArchitecture(temporary))
60 throw new Error(`C compiler did not produce a Linux ${values.arch} executable.`);
61 NodeFS.renameSync(temporary, output);
62 } finally {
63 NodeFS.rmSync(temporary, { force: true });
64 }
65 }
66}