Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
230 lines
7.0 KiB
JavaScript
230 lines
7.0 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import * as path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const sourceFile = path.join(scriptDir, "src", "win32-console-mode.c");
|
|
const temporaryDir = mkdtempSync(path.join(tmpdir(), "pi-tui-win32-"));
|
|
|
|
const targets = [
|
|
{
|
|
arch: "x64",
|
|
msvcArch: "x64",
|
|
mingwTarget: "x86_64-w64-windows-gnu",
|
|
prefixedCompilers: ["x86_64-w64-mingw32-gcc", "x86_64-w64-mingw32-clang"],
|
|
envCompiler: "CC_X64",
|
|
},
|
|
{
|
|
arch: "arm64",
|
|
msvcArch: "arm64",
|
|
mingwTarget: "aarch64-w64-windows-gnu",
|
|
prefixedCompilers: ["aarch64-w64-mingw32-gcc", "aarch64-w64-mingw32-clang"],
|
|
envCompiler: "CC_ARM64",
|
|
},
|
|
];
|
|
|
|
function removeTemporaryDir() {
|
|
rmSync(temporaryDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
|
|
process.on("exit", removeTemporaryDir);
|
|
process.on("SIGINT", () => {
|
|
removeTemporaryDir();
|
|
process.exit(130);
|
|
});
|
|
|
|
function commandExists(command) {
|
|
const result = spawnSync(command, ["--version"], { stdio: "ignore" });
|
|
return !result.error || result.error.code !== "ENOENT";
|
|
}
|
|
|
|
function run(command, args, options = {}) {
|
|
const result = spawnSync(command, args, { stdio: "inherit", ...options });
|
|
if (result.error) throw result.error;
|
|
if (result.status !== 0) throw new Error(`${command} failed with exit code ${result.status ?? 1}`);
|
|
}
|
|
|
|
function quoteBatch(value) {
|
|
return `"${value.replaceAll('"', '""')}"`;
|
|
}
|
|
|
|
function msvcHostArch() {
|
|
return process.arch === "arm64" ? "arm64" : "x64";
|
|
}
|
|
|
|
function findVisualStudioDevCmd() {
|
|
const candidates = [];
|
|
const programFilesX86 = process.env["ProgramFiles(x86)"];
|
|
if (programFilesX86) {
|
|
const vswhere = path.join(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
|
|
if (existsSync(vswhere)) {
|
|
const result = spawnSync(
|
|
vswhere,
|
|
[
|
|
"-latest",
|
|
"-products",
|
|
"*",
|
|
"-requires",
|
|
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
|
"-property",
|
|
"installationPath",
|
|
],
|
|
{ encoding: "utf8" },
|
|
);
|
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
const installationPath = line.trim();
|
|
if (installationPath) candidates.push(path.join(installationPath, "Common7", "Tools", "VsDevCmd.bat"));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const base of [process.env.ProgramFiles, programFilesX86]) {
|
|
if (!base) continue;
|
|
for (const edition of ["BuildTools", "Community", "Professional", "Enterprise"]) {
|
|
candidates.push(path.join(base, "Microsoft Visual Studio", "2022", edition, "Common7", "Tools", "VsDevCmd.bat"));
|
|
}
|
|
}
|
|
|
|
return candidates.find((candidate) => existsSync(candidate));
|
|
}
|
|
|
|
function canUseMsvc(vsDevCmd) {
|
|
const probeDir = path.join(temporaryDir, "msvc-probe");
|
|
mkdirSync(probeDir, { recursive: true });
|
|
const batchFile = path.join(probeDir, "probe.cmd");
|
|
writeFileSync(
|
|
batchFile,
|
|
[
|
|
"@echo off",
|
|
`call ${quoteBatch(vsDevCmd)} -no_logo -arch=x64 -host_arch=${msvcHostArch()} >nul`,
|
|
"if errorlevel 1 exit /b %errorlevel%",
|
|
"where cl.exe >nul 2>nul",
|
|
].join("\r\n"),
|
|
);
|
|
const result = spawnSync("cmd.exe", ["/d", "/s", "/c", batchFile], { cwd: probeDir, stdio: "ignore" });
|
|
return !result.error && result.status === 0;
|
|
}
|
|
|
|
function resolveMingwCompiler(target) {
|
|
const envCompiler = process.env[target.envCompiler];
|
|
if (envCompiler) return { command: envCompiler, useTargetFlag: false };
|
|
if (process.env.CC) return { command: process.env.CC, useTargetFlag: true };
|
|
for (const command of target.prefixedCompilers) {
|
|
if (commandExists(command)) return { command, useTargetFlag: false };
|
|
}
|
|
if (commandExists("clang")) return { command: "clang", useTargetFlag: true };
|
|
return undefined;
|
|
}
|
|
|
|
function installOutput(temporaryOutput, target) {
|
|
const outputDir = path.join(scriptDir, "prebuilds", `win32-${target.arch}`);
|
|
mkdirSync(outputDir, { recursive: true });
|
|
const output = path.join(outputDir, "win32-console-mode.node");
|
|
copyFileSync(temporaryOutput, output);
|
|
chmodSync(output, 0o755);
|
|
console.log(`Built ${path.relative(process.cwd(), output)}`);
|
|
}
|
|
|
|
function buildWithMsvc(target, vsDevCmd) {
|
|
const buildDir = path.join(temporaryDir, `msvc-${target.arch}`);
|
|
const temporaryOutput = path.join(buildDir, "win32-console-mode.node");
|
|
mkdirSync(buildDir, { recursive: true });
|
|
|
|
const batchFile = path.join(buildDir, "build.cmd");
|
|
writeFileSync(
|
|
batchFile,
|
|
[
|
|
"@echo off",
|
|
`call ${quoteBatch(vsDevCmd)} -no_logo -arch=${target.msvcArch} -host_arch=${msvcHostArch()}`,
|
|
"if errorlevel 1 exit /b %errorlevel%",
|
|
[
|
|
"cl",
|
|
"/nologo",
|
|
"/TC",
|
|
"/std:c11",
|
|
"/W4",
|
|
"/O1",
|
|
"/GS-",
|
|
"/LD",
|
|
`/Fe:${quoteBatch(temporaryOutput)}`,
|
|
quoteBatch(sourceFile),
|
|
"/link",
|
|
"/NOENTRY",
|
|
"/NODEFAULTLIB",
|
|
"kernel32.lib",
|
|
"/OPT:REF",
|
|
"/OPT:ICF",
|
|
].join(" "),
|
|
"if errorlevel 1 exit /b %errorlevel%",
|
|
].join("\r\n"),
|
|
);
|
|
|
|
run("cmd.exe", ["/d", "/s", "/c", batchFile], { cwd: buildDir });
|
|
installOutput(temporaryOutput, target);
|
|
}
|
|
|
|
function buildWithMingw(target) {
|
|
const compiler = resolveMingwCompiler(target);
|
|
if (!compiler) {
|
|
throw new Error(
|
|
`No Windows cross-compiler found for ${target.arch}. Install Microsoft C++ Build Tools on Windows, or set ${target.envCompiler} to a MinGW-compatible compiler.`,
|
|
);
|
|
}
|
|
|
|
const buildDir = path.join(temporaryDir, `mingw-${target.arch}`);
|
|
const temporaryOutput = path.join(buildDir, "win32-console-mode.node");
|
|
mkdirSync(buildDir, { recursive: true });
|
|
|
|
const args = [
|
|
...(compiler.useTargetFlag ? [`--target=${target.mingwTarget}`] : []),
|
|
"-std=c11",
|
|
"-Wall",
|
|
"-Wextra",
|
|
"-Oz",
|
|
"-shared",
|
|
"-nostdlib",
|
|
"-Wl,--no-entry",
|
|
"-Wl,--strip-all",
|
|
sourceFile,
|
|
"-lkernel32",
|
|
"-o",
|
|
temporaryOutput,
|
|
];
|
|
|
|
run(compiler.command, args, { cwd: buildDir });
|
|
installOutput(temporaryOutput, target);
|
|
}
|
|
|
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
console.log(`Usage: npm --prefix packages/tui run build:native:win32
|
|
|
|
Builds win32-x64 and win32-arm64 native prebuilds.
|
|
|
|
Environment:
|
|
PI_TUI_WIN32_TOOLCHAIN=msvc|mingw
|
|
CC_X64=/path/to/x86_64-w64-mingw32-gcc
|
|
CC_ARM64=/path/to/aarch64-w64-mingw32-gcc
|
|
CC=/path/to/clang`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const requestedToolchain = process.env.PI_TUI_WIN32_TOOLCHAIN;
|
|
if (requestedToolchain && requestedToolchain !== "msvc" && requestedToolchain !== "mingw") {
|
|
throw new Error("PI_TUI_WIN32_TOOLCHAIN must be either 'msvc' or 'mingw'");
|
|
}
|
|
|
|
const vsDevCmd = findVisualStudioDevCmd();
|
|
const hasMsvc = vsDevCmd ? canUseMsvc(vsDevCmd) : false;
|
|
const toolchain = requestedToolchain ?? (process.platform === "win32" && vsDevCmd ? "msvc" : "mingw");
|
|
|
|
if (toolchain === "msvc") {
|
|
if (!vsDevCmd || !hasMsvc) {
|
|
throw new Error("Microsoft C++ Build Tools not found. Install the Visual Studio C++ workload or set PI_TUI_WIN32_TOOLCHAIN=mingw.");
|
|
}
|
|
for (const target of targets) buildWithMsvc(target, vsDevCmd);
|
|
} else {
|
|
for (const target of targets) buildWithMingw(target);
|
|
}
|