| 1 | import * as NodeChildProcess from "node:child_process"; |
| 2 | import * as NodeFS from "node:fs"; |
| 3 | import * as NodePath from "node:path"; |
| 4 | import * as NodeURL from "node:url"; |
| 5 | import * 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. |
| 8 | const hostArch = process.arch; |
| 9 | // oxlint-disable-next-line t3code/no-global-process-runtime -- Native compilation only runs on the actual Linux host. |
| 10 | const hostPlatform = process.platform; |
| 11 | |
| 12 | const { values } = NodeUtil.parseArgs({ |
| 13 | options: { output: { type: "string" }, arch: { type: "string", default: hostArch } }, |
| 14 | }); |
| 15 | |
| 16 | if (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 | } |