xbintsc 0.5.52 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/README.zh-CN.md +9 -3
- package/dist/scripts/fetch-tarball.d.ts +28 -0
- package/dist/scripts/fetch-tarball.js +57 -0
- package/dist/scripts/fetch-tarball.js.map +1 -0
- package/dist/scripts/gui-build-options.d.ts +77 -0
- package/dist/scripts/gui-build-options.js +134 -0
- package/dist/scripts/gui-build-options.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/hints.d.ts +6 -2
- package/dist/src/cli/hints.js +16 -2
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +9 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/driver/app-artifacts.d.ts +2 -0
- package/dist/src/driver/app-artifacts.js +3 -3
- package/dist/src/driver/app-artifacts.js.map +1 -1
- package/dist/src/driver/compiler.js +147 -52
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/icon.d.ts +1 -1
- package/dist/src/driver/icon.js +3 -3
- package/dist/src/driver/icon.js.map +1 -1
- package/dist/src/driver/macos-sdk.d.ts +32 -0
- package/dist/src/driver/macos-sdk.js +50 -0
- package/dist/src/driver/macos-sdk.js.map +1 -0
- package/dist/src/driver/options.d.ts +6 -0
- package/dist/src/driver/platform-support.d.ts +45 -0
- package/dist/src/driver/platform-support.js +66 -0
- package/dist/src/driver/platform-support.js.map +1 -0
- package/dist/src/driver/prebuilt.d.ts +26 -0
- package/dist/src/driver/prebuilt.js +58 -0
- package/dist/src/driver/prebuilt.js.map +1 -0
- package/dist/src/driver/runtime-objects.d.ts +11 -0
- package/dist/src/driver/runtime-objects.js +14 -5
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/toolchain-provider.d.ts +8 -0
- package/dist/src/driver/toolchain-provider.js +8 -0
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +30 -1
- package/dist/src/driver/toolchain.js +88 -8
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/driver/version.js.map +1 -1
- package/dist/src/driver/win-entry.d.ts +1 -1
- package/dist/src/driver/win-entry.js +4 -4
- package/dist/src/driver/win-entry.js.map +1 -1
- package/dist/src/extensions/gui/html.d.ts +10 -7
- package/dist/src/extensions/gui/html.js +53 -29
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/node/child_process/index.d.ts +4 -3
- package/dist/src/extensions/node/child_process/index.js +4 -3
- package/dist/src/extensions/node/child_process/index.js.map +1 -1
- package/dist/tests/cli/hints.test.js +9 -1
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/cli/main.test.js +9 -0
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/compiler.test.js +107 -0
- package/dist/tests/driver/compiler.test.js.map +1 -1
- package/dist/tests/driver/cxx-compile-flags.test.d.ts +1 -0
- package/dist/tests/driver/cxx-compile-flags.test.js +24 -0
- package/dist/tests/driver/cxx-compile-flags.test.js.map +1 -0
- package/dist/tests/driver/macos-sdk.test.d.ts +1 -0
- package/dist/tests/driver/macos-sdk.test.js +83 -0
- package/dist/tests/driver/macos-sdk.test.js.map +1 -0
- package/dist/tests/driver/platform-support.test.d.ts +1 -0
- package/dist/tests/driver/platform-support.test.js +41 -0
- package/dist/tests/driver/platform-support.test.js.map +1 -0
- package/dist/tests/driver/prebuilt.test.d.ts +1 -0
- package/dist/tests/driver/prebuilt.test.js +44 -0
- package/dist/tests/driver/prebuilt.test.js.map +1 -0
- package/dist/tests/driver/toolchain-provider.test.js +24 -0
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +46 -2
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +4 -2
- package/dist/tests/e2e/async-await.test.js.map +1 -1
- package/dist/tests/e2e/compile-node.test.js +21 -0
- package/dist/tests/e2e/compile-node.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.js +34 -6
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-pelican3d.test.d.ts +10 -0
- package/dist/tests/e2e/gui-example-pelican3d.test.js +94 -0
- package/dist/tests/e2e/gui-example-pelican3d.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.js +48 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -1
- package/dist/tests/e2e/unhandled-rejection.test.d.ts +14 -0
- package/dist/tests/e2e/unhandled-rejection.test.js +107 -0
- package/dist/tests/e2e/unhandled-rejection.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +44 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/dist/tests/scripts/fetch-tarball.test.d.ts +1 -0
- package/dist/tests/scripts/fetch-tarball.test.js +70 -0
- package/dist/tests/scripts/fetch-tarball.test.js.map +1 -0
- package/doc/ai/cli.md +6 -0
- package/doc/ai/language-support.md +1 -0
- package/doc/ai/troubleshooting.md +32 -2
- package/doc/ai/zh-CN/cli.md +5 -0
- package/doc/ai/zh-CN/language-support.md +1 -0
- package/doc/ai/zh-CN/troubleshooting.md +27 -2
- package/doc/gui/build.md +7 -3
- package/doc/gui/dom.md +3 -1
- package/doc/gui/webgl.md +4 -0
- package/doc/gui-scripts.md +33 -17
- package/doc/gui.md +17 -1
- package/doc/implemented/async.md +1 -0
- package/doc/implemented/modules.md +9 -6
- package/doc/implemented/node/child-process.md +12 -4
- package/doc/implemented/runtime-core.md +1 -1
- package/doc/npm-package.md +23 -2
- package/doc/post/make-js-great-again.md +30 -0
- package/doc/requirements.md +36 -3
- package/doc/unimplemented/deviations.md +1 -0
- package/doc/zh-CN/gui/build.md +4 -2
- package/doc/zh-CN/gui/dom.md +3 -1
- package/doc/zh-CN/gui/webgl.md +1 -0
- package/doc/zh-CN/gui-scripts.md +23 -11
- package/doc/zh-CN/gui.md +11 -1
- package/doc/zh-CN/implemented/async.md +1 -0
- package/doc/zh-CN/implemented/modules.md +8 -4
- package/doc/zh-CN/implemented/node/child-process.md +10 -3
- package/doc/zh-CN/implemented/runtime-core.md +1 -1
- package/doc/zh-CN/npm-package.md +18 -1
- package/doc/zh-CN/post/make-js-great-again.md +32 -0
- package/doc/zh-CN/requirements.md +30 -2
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- package/package.json +7 -7
- package/runtime/ext_node/child_process/child_process.c +149 -7
- package/runtime/rt.h +4 -0
- package/runtime/xt_async.c +4 -0
- package/runtime/xt_generator.c +2 -0
- package/runtime/xt_loop.c +8 -4
- package/runtime/xt_promise.c +103 -0
- package/scripts/build-gui.ts +17 -5
- package/scripts/build-media.ts +89 -10
- package/scripts/build-runtime.ts +7 -2
- package/scripts/fetch-tarball.ts +71 -0
- package/scripts/gui-build-cache.ts +28 -0
- package/scripts/gui-build-options.ts +26 -2
- package/scripts/package-release.ts +8 -0
- package/src/cli/hints.ts +19 -2
- package/src/cli/main.ts +11 -0
- package/src/driver/app-artifacts.ts +5 -3
- package/src/driver/compiler.ts +167 -56
- package/src/driver/icon.ts +3 -2
- package/src/driver/macos-sdk.ts +52 -0
- package/src/driver/options.ts +6 -0
- package/src/driver/platform-support.ts +69 -0
- package/src/driver/prebuilt.ts +61 -0
- package/src/driver/runtime-objects.ts +26 -5
- package/src/driver/toolchain-provider.ts +11 -0
- package/src/driver/toolchain.ts +105 -9
- package/src/driver/version.ts +1 -1
- package/src/driver/win-entry.ts +4 -3
- package/src/extensions/gui/html.ts +61 -31
- package/src/extensions/node/child_process/index.ts +4 -3
package/src/cli/hints.ts
CHANGED
|
@@ -36,10 +36,27 @@ const REQUIREMENT_HINT: DocumentationPointer = {
|
|
|
36
36
|
path: DOC_REQUIREMENTS,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Hint for a clang failure: a missing or too-old clang has a specific answer.
|
|
41
|
+
*
|
|
42
|
+
* `platform` is a parameter so the macOS branch can be tested from any host.
|
|
43
|
+
*/
|
|
44
|
+
export function toolchainPointer(
|
|
45
|
+
message: string,
|
|
46
|
+
platform: NodeJS.Platform = process.platform,
|
|
47
|
+
): DocumentationPointer {
|
|
41
48
|
const usesTypedPointers = /defined with type '\[.*i8\]\*' but expected 'i8\*'/.test(message);
|
|
42
49
|
if (usesTypedPointers || /No C compiler found/i.test(message)) return REQUIREMENT_HINT;
|
|
50
|
+
/* On macOS a C standard header lives in the SDK, so "'<header>' file not
|
|
51
|
+
* found" means the driver never got a sysroot — the header itself is fine.
|
|
52
|
+
* The driver resolves the SDK with `xcrun` and passes `-isysroot`, which
|
|
53
|
+
* makes the selected Xcode the thing to check. */
|
|
54
|
+
if (platform === "darwin" && /fatal error: '.+' file not found/.test(message)) {
|
|
55
|
+
return {
|
|
56
|
+
text: "clang cannot see the macOS SDK headers; `xcrun --show-sdk-path` must resolve for the selected Xcode, or set SDKROOT yourself",
|
|
57
|
+
path: DOC_TROUBLESHOOTING,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
43
60
|
return {
|
|
44
61
|
text: "read the clang output above, then the toolchain section of the troubleshooting guide",
|
|
45
62
|
path: DOC_TROUBLESHOOTING,
|
package/src/cli/main.ts
CHANGED
|
@@ -20,9 +20,11 @@ import { formatDiagnostic, type Diagnostic } from "../diagnostics/diagnostic.js"
|
|
|
20
20
|
import { SourceFile } from "../diagnostics/source.js";
|
|
21
21
|
import { build, compileEntry, COMPILER_VERSION, type EmitKind, type PhaseTiming } from "../driver/compiler.js";
|
|
22
22
|
import { findRuntimeDir, platformSlug } from "../driver/paths.js";
|
|
23
|
+
import { supportFloor } from "../driver/platform-support.js";
|
|
23
24
|
import { findRuntimeLibrary, runtimeLibDir } from "../driver/runtime-lib.js";
|
|
24
25
|
import { resolveToolchain, type ResolvedToolchain } from "../driver/toolchain-provider.js";
|
|
25
26
|
import { MINIMUM_CLANG_MAJOR, probeClang, realRunner, ToolchainError } from "../driver/toolchain.js";
|
|
27
|
+
import { macosSdkRoot } from "../driver/macos-sdk.js";
|
|
26
28
|
import {
|
|
27
29
|
DOC_CLI,
|
|
28
30
|
DOC_TROUBLESHOOTING,
|
|
@@ -277,6 +279,15 @@ function doctor(io: CliIo): number {
|
|
|
277
279
|
io.stdout("runtime lib: unavailable\n");
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
io.stdout(`support : ${supportFloor()} (release artifacts target this floor)\n`);
|
|
283
|
+
|
|
284
|
+
if (process.platform === "darwin") {
|
|
285
|
+
/* macOS keeps the C headers in the SDK; without one, no header the runtime
|
|
286
|
+
* includes resolves. Report the sysroot the driver hands to clang. */
|
|
287
|
+
const sdk = toolchain?.sdkRoot ?? process.env.SDKROOT ?? macosSdkRoot(realRunner);
|
|
288
|
+
io.stdout(`sdk : ${sdk ?? "unresolved (xcrun --show-sdk-path failed)"}\n`);
|
|
289
|
+
}
|
|
290
|
+
|
|
280
291
|
try {
|
|
281
292
|
if (!toolchain) throw new Error("no toolchain");
|
|
282
293
|
const resource = resolveResourceCompiler(realRunner, toolchain.clang);
|
|
@@ -22,6 +22,8 @@ export interface AppArtifactRequest {
|
|
|
22
22
|
readonly app: AppConfig;
|
|
23
23
|
readonly icon: IconInfo | undefined;
|
|
24
24
|
readonly env: Record<string, string>;
|
|
25
|
+
/** macOS SDK for `-isysroot`; see `sysrootFlags`. */
|
|
26
|
+
readonly sdkRoot?: string;
|
|
25
27
|
readonly verbose: boolean | undefined;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -46,11 +48,11 @@ export interface AppArtifacts {
|
|
|
46
48
|
* no equivalent.
|
|
47
49
|
*/
|
|
48
50
|
export function ensureAppArtifacts(request: AppArtifactRequest): AppArtifacts {
|
|
49
|
-
const { runner, clang, cacheDir, registry, app, icon, env, verbose } = request;
|
|
51
|
+
const { runner, clang, cacheDir, registry, app, icon, env, sdkRoot, verbose } = request;
|
|
50
52
|
const windowsSubsystem = process.platform === "win32" && registry.has("gui") && app.console !== true;
|
|
51
53
|
const linkerFlags = windowsSubsystem ? ["-Wl,/SUBSYSTEM:WINDOWS"] : [];
|
|
52
54
|
const entryObject = windowsSubsystem
|
|
53
|
-
? ensureWindowsEntryObject(runner, clang, cacheDir, env)
|
|
55
|
+
? ensureWindowsEntryObject(runner, clang, cacheDir, env, sdkRoot)
|
|
54
56
|
: undefined;
|
|
55
57
|
|
|
56
58
|
const iconObjects: string[] = [];
|
|
@@ -58,7 +60,7 @@ export function ensureAppArtifacts(request: AppArtifactRequest): AppArtifacts {
|
|
|
58
60
|
// program gets an (empty) icon object even with no icon configured.
|
|
59
61
|
if (icon !== undefined || registry.has("gui")) {
|
|
60
62
|
try {
|
|
61
|
-
iconObjects.push(ensureIconObject(runner, clang, cacheDir, icon ?? EMPTY_ICON, env));
|
|
63
|
+
iconObjects.push(ensureIconObject(runner, clang, cacheDir, icon ?? EMPTY_ICON, env, sdkRoot));
|
|
62
64
|
if (icon && process.platform === "win32") {
|
|
63
65
|
const resourceCompiler = resolveResourceCompiler(runner, clang);
|
|
64
66
|
if (resourceCompiler) {
|
package/src/driver/compiler.ts
CHANGED
|
@@ -18,18 +18,20 @@ import { DiagnosticBag, DiagnosticCode } from "../diagnostics/diagnostic.js";
|
|
|
18
18
|
import { generate } from "../codegen/llvm.js";
|
|
19
19
|
import { createDefaultRegistry } from "../extensions/registry.js";
|
|
20
20
|
import { BuildCache } from "./cache.js";
|
|
21
|
-
import { findRuntimeDir } from "./paths.js";
|
|
22
|
-
import { resolveToolchain } from "./toolchain-provider.js";
|
|
21
|
+
import { findRuntimeDir, platformSlug } from "./paths.js";
|
|
22
|
+
import { resolveToolchain, type ResolvedToolchain } from "./toolchain-provider.js";
|
|
23
23
|
import { readIcon, IconError, type IconInfo } from "./icon.js";
|
|
24
24
|
import { bundlePathFor, packageMacApp } from "./mac-bundle.js";
|
|
25
25
|
import type { AppConfig } from "./config.js";
|
|
26
26
|
import { compileIr, link, mergeProfileData, realRunner, ToolchainError } from "./toolchain.js";
|
|
27
27
|
import { parseEntry } from "./frontend.js";
|
|
28
|
-
import { ensureRuntimeObjects } from "./runtime-objects.js";
|
|
28
|
+
import { ensureRuntimeObjects, type RuntimeObjects } from "./runtime-objects.js";
|
|
29
29
|
import { ensureAppArtifacts } from "./app-artifacts.js";
|
|
30
30
|
import { buildCacheKey } from "./build-key.js";
|
|
31
31
|
import { collectUsage } from "./usage.js";
|
|
32
32
|
import { resolveLinkFeatures, type ResolvedLinkFeature } from "./link-features.js";
|
|
33
|
+
import { isPrebuiltArchivePath, firstFailureLine, looksLikePrebuiltLinkFailure } from "./prebuilt.js";
|
|
34
|
+
import { runtimeLibDirs as defaultRuntimeLibDirs } from "./runtime-lib.js";
|
|
33
35
|
import type { BuildOptions, BuildResult, EmitKind, PhaseTiming } from "./options.js";
|
|
34
36
|
|
|
35
37
|
export { COMPILER_VERSION } from "./version.js";
|
|
@@ -68,6 +70,36 @@ function executableName(entry: string, outDir: string, explicit: string | undefi
|
|
|
68
70
|
return resolve(outDir, base + suffix);
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Explain a retry that still fails while prebuilt `gui`/`media` archives are in
|
|
75
|
+
* the link. Unlike `core`/`ext_node`, those bundle SDL3/FFmpeg/glslang and
|
|
76
|
+
* their sources are not part of the npm package, so the source fallback cannot
|
|
77
|
+
* replace them; only a release/toolchain match can.
|
|
78
|
+
*/
|
|
79
|
+
function prebuiltFallbackError(
|
|
80
|
+
error: unknown,
|
|
81
|
+
runtimeObjects: RuntimeObjects,
|
|
82
|
+
dirs: readonly string[] | undefined,
|
|
83
|
+
): unknown {
|
|
84
|
+
if (!(error instanceof ToolchainError) || !looksLikePrebuiltLinkFailure(error.stderr)) return error;
|
|
85
|
+
const runtimeDirs = dirs ?? defaultRuntimeLibDirs();
|
|
86
|
+
const fixed = [...runtimeObjects.featureObjects, ...runtimeObjects.nativeObjects].filter((object) =>
|
|
87
|
+
isPrebuiltArchivePath(object, runtimeDirs),
|
|
88
|
+
);
|
|
89
|
+
if (fixed.length === 0) return error;
|
|
90
|
+
const archives = [...new Set(fixed.map((object) => basename(object)))].join(", ");
|
|
91
|
+
return new ToolchainError(
|
|
92
|
+
error.command,
|
|
93
|
+
error.status,
|
|
94
|
+
`${error.stderr}\n` +
|
|
95
|
+
`The remaining prebuilt archives (${archives}) come from the @xbintsc/${platformSlug()} ` +
|
|
96
|
+
"package. Unlike the C runtime, they bundle SDL3/FFmpeg, whose sources are not shipped " +
|
|
97
|
+
"in the npm package, so they cannot be rebuilt here.\n" +
|
|
98
|
+
"Build with the Xcode Command Line Tools / Visual Studio C++ toolset the release was built " +
|
|
99
|
+
"with, or use a release archive built for this machine.",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
71
103
|
export function build(entryPath: string, options: BuildOptions = {}): BuildResult {
|
|
72
104
|
const runner = options.runner ?? realRunner;
|
|
73
105
|
const registry = options.extensions ?? createDefaultRegistry();
|
|
@@ -220,8 +252,8 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
220
252
|
|
|
221
253
|
// -- toolchain -----------------------------------------------------------
|
|
222
254
|
const clangStarted = Date.now();
|
|
223
|
-
const toolchain = options.clang
|
|
224
|
-
? { clang: options.clang, linkerArgs: []
|
|
255
|
+
const toolchain: Pick<ResolvedToolchain, "clang" | "linkerArgs" | "env" | "sdkRoot"> = options.clang
|
|
256
|
+
? { clang: options.clang, linkerArgs: [], env: {} }
|
|
225
257
|
: resolveToolchain(runner);
|
|
226
258
|
const clang = toolchain.clang;
|
|
227
259
|
if (pgo === "use") {
|
|
@@ -244,6 +276,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
244
276
|
objectPath,
|
|
245
277
|
optimize,
|
|
246
278
|
env: toolchain.env,
|
|
279
|
+
sdkRoot: toolchain.sdkRoot,
|
|
247
280
|
debug: options.debug === true,
|
|
248
281
|
lto,
|
|
249
282
|
...(pgo ? { pgo, pgoDir } : {}),
|
|
@@ -256,21 +289,32 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
256
289
|
return { outputPath, irPath, cached: false, diagnostics: [], ir, timings };
|
|
257
290
|
}
|
|
258
291
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
292
|
+
/* The runtime is assembled before the app artifacts so a failed prebuilt
|
|
293
|
+
* link can be retried with the same artifact inputs. */
|
|
294
|
+
const runtimeLibDirs = options.runtimeLibDirs;
|
|
295
|
+
const preferPrebuilt = resolvePreferPrebuilt(options.preferPrebuilt) && !lto && pgo === undefined;
|
|
296
|
+
const assembleRuntime = (usePrebuilt: boolean): RuntimeObjects => {
|
|
297
|
+
const started = Date.now();
|
|
298
|
+
const assembled = ensureRuntimeObjects({
|
|
299
|
+
runner,
|
|
300
|
+
clang,
|
|
301
|
+
runtimeDir,
|
|
302
|
+
cacheDir,
|
|
303
|
+
registry,
|
|
304
|
+
preferPrebuilt: usePrebuilt,
|
|
305
|
+
env: toolchain.env,
|
|
306
|
+
sdkRoot: toolchain.sdkRoot,
|
|
307
|
+
lto,
|
|
308
|
+
pgo,
|
|
309
|
+
pgoDir,
|
|
310
|
+
features: linkFeatures,
|
|
311
|
+
...(runtimeLibDirs !== undefined ? { runtimeLibDirs } : {}),
|
|
312
|
+
});
|
|
313
|
+
timings.push({ phase: "runtime", ms: Date.now() - started });
|
|
314
|
+
return assembled;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
let assembled = assembleRuntime(preferPrebuilt);
|
|
274
318
|
|
|
275
319
|
const artifacts = ensureAppArtifacts({
|
|
276
320
|
runner,
|
|
@@ -280,6 +324,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
280
324
|
app,
|
|
281
325
|
icon,
|
|
282
326
|
env: toolchain.env,
|
|
327
|
+
sdkRoot: toolchain.sdkRoot,
|
|
283
328
|
verbose: options.verbose,
|
|
284
329
|
});
|
|
285
330
|
if (artifacts.iconError !== undefined) {
|
|
@@ -287,42 +332,108 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
287
332
|
return { outputPath, irPath, cached: false, diagnostics: diagnostics.diagnostics, ir };
|
|
288
333
|
}
|
|
289
334
|
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
...
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
335
|
+
const linkAssembled = (runtimeObjects: RuntimeObjects): string => {
|
|
336
|
+
const started = Date.now();
|
|
337
|
+
const result = link(runner, {
|
|
338
|
+
clang,
|
|
339
|
+
objectPaths: [
|
|
340
|
+
objectPath,
|
|
341
|
+
/* Feature archives come after their bridges (which reference the
|
|
342
|
+
* implementation) and before the extensions' own archives (`gui.a`
|
|
343
|
+
* provides symbols the bridges call back into). `Set` keeps that order
|
|
344
|
+
* and drops an archive two features or an extension share. */
|
|
345
|
+
...new Set([
|
|
346
|
+
...runtimeObjects.runtimeObjects,
|
|
347
|
+
...runtimeObjects.extensionObjects,
|
|
348
|
+
...runtimeObjects.featureObjects,
|
|
349
|
+
...runtimeObjects.nativeObjects,
|
|
350
|
+
...(artifacts.entryObject !== undefined ? [artifacts.entryObject] : []),
|
|
351
|
+
...artifacts.iconObjects,
|
|
352
|
+
]),
|
|
353
|
+
],
|
|
354
|
+
outputPath,
|
|
355
|
+
linkerFlags: [
|
|
356
|
+
...toolchain.linkerArgs,
|
|
357
|
+
...(process.platform === "win32" ? ["-lws2_32"] : ["-lm", "-pthread"]),
|
|
358
|
+
...registry.linkerFlags(),
|
|
359
|
+
...linkFeatures.flatMap((feature) => feature.linkerFlags),
|
|
360
|
+
/* A GUI program is a Windows-subsystem executable: no console window is
|
|
361
|
+
* created when it is launched. Console output (stdout/stderr) is lost in
|
|
362
|
+
* that subsystem, so a program that wants to report things opts back in
|
|
363
|
+
* with `app.console: true`. */
|
|
364
|
+
...artifacts.linkerFlags,
|
|
365
|
+
],
|
|
366
|
+
optimize,
|
|
367
|
+
env: toolchain.env,
|
|
368
|
+
sdkRoot: toolchain.sdkRoot,
|
|
369
|
+
lto,
|
|
370
|
+
...(pgo ? { pgo, pgoDir } : {}),
|
|
371
|
+
});
|
|
372
|
+
timings.push({ phase: "link", ms: Date.now() - started });
|
|
373
|
+
return result.stderr;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Retry once with the C runtime compiled from the sources shipped in the
|
|
378
|
+
* package. The failed attempt is not cached: a later toolchain upgrade can
|
|
379
|
+
* use the archive again.
|
|
380
|
+
*/
|
|
381
|
+
const retryFromSource = (stderr: string): void => {
|
|
382
|
+
const reason = firstFailureLine(stderr);
|
|
383
|
+
process.stderr.write(
|
|
384
|
+
"xbintsc: the prebuilt runtime archive does not match this toolchain" +
|
|
385
|
+
(reason ? ` (${reason})` : "") +
|
|
386
|
+
"; compiling the C runtime from source\n",
|
|
387
|
+
);
|
|
388
|
+
assembled = assembleRuntime(false);
|
|
389
|
+
try {
|
|
390
|
+
linkAssembled(assembled);
|
|
391
|
+
} catch (retryError) {
|
|
392
|
+
throw prebuiltFallbackError(retryError, assembled, runtimeLibDirs);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const prebuiltInUse = (): boolean =>
|
|
396
|
+
preferPrebuilt && assembled.prebuiltArchives.length > 0;
|
|
397
|
+
/** Whether the linker talked about an archive the source fallback can replace. */
|
|
398
|
+
const mentionsRebuildableArchive = (stderr: string): boolean =>
|
|
399
|
+
assembled.prebuiltArchives.some((archive) => stderr.includes(basename(archive)));
|
|
400
|
+
|
|
401
|
+
/* One attempt, captured instead of thrown so the retry below is not itself
|
|
402
|
+
* caught by this decision block. `failed` is explicit because a thrown
|
|
403
|
+
* `undefined` must not look like a successful link. */
|
|
404
|
+
let failed = false;
|
|
405
|
+
let firstError: unknown;
|
|
406
|
+
let firstStderr = "";
|
|
407
|
+
try {
|
|
408
|
+
firstStderr = linkAssembled(assembled);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
failed = true;
|
|
411
|
+
firstError = error;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (failed) {
|
|
415
|
+
const error = firstError;
|
|
416
|
+
/* The release runner's Xcode/SDK or Visual Studio toolset can be newer
|
|
417
|
+
* than this machine's, and then the prebuilt archive cannot be linked
|
|
418
|
+
* (`LNK2038`, `LNK2019`, `Undefined symbols`, ...). The C runtime sources
|
|
419
|
+
* ship with the package, so retry once with them instead of failing on a
|
|
420
|
+
* linker message the user cannot act on. */
|
|
421
|
+
if (!prebuiltInUse() || !(error instanceof ToolchainError) || !looksLikePrebuiltLinkFailure(error.stderr)) {
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
retryFromSource(error.stderr);
|
|
425
|
+
} else if (
|
|
426
|
+
prebuiltInUse() &&
|
|
427
|
+
mentionsRebuildableArchive(firstStderr) &&
|
|
428
|
+
looksLikePrebuiltLinkFailure(firstStderr)
|
|
429
|
+
) {
|
|
430
|
+
/* A successful link can still warn that the archive was built for a newer
|
|
431
|
+
* macOS than the one being targeted: the binary would then claim the local
|
|
432
|
+
* deployment target while calling newer SDK symbols at run time, so rebuild
|
|
433
|
+
* it from source just like an outright failure. Only `core`/`ext_node` can
|
|
434
|
+
* be replaced this way; a warning about `gui.a`/`media.a` is left alone. */
|
|
435
|
+
retryFromSource(firstStderr);
|
|
436
|
+
}
|
|
326
437
|
|
|
327
438
|
const bundlePath =
|
|
328
439
|
bundle !== undefined
|
package/src/driver/icon.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { join, resolve } from "node:path";
|
|
18
18
|
import { hashParts } from "./cache.js";
|
|
19
|
-
import { compileC, type Runner } from "./toolchain.js";
|
|
19
|
+
import { compileC, sysrootFlags, type Runner } from "./toolchain.js";
|
|
20
20
|
|
|
21
21
|
export type IconFormat = "png" | "ico" | "icns";
|
|
22
22
|
|
|
@@ -144,6 +144,7 @@ export function ensureIconObject(
|
|
|
144
144
|
cacheDir: string,
|
|
145
145
|
icon: IconInfo,
|
|
146
146
|
env?: Record<string, string>,
|
|
147
|
+
sdkRoot?: string,
|
|
147
148
|
): string {
|
|
148
149
|
const key = hashParts(["icon", process.platform, Buffer.from(icon.bytes).toString("base64")]);
|
|
149
150
|
const sourcePath = join(cacheDir, `icon-${key}.c`);
|
|
@@ -151,7 +152,7 @@ export function ensureIconObject(
|
|
|
151
152
|
if (!existsSync(objectPath)) {
|
|
152
153
|
mkdirSync(cacheDir, { recursive: true });
|
|
153
154
|
writeFileSync(sourcePath, iconSource(icon));
|
|
154
|
-
compileC(runner, clang, sourcePath, objectPath, cacheDir, env);
|
|
155
|
+
compileC(runner, clang, sourcePath, objectPath, cacheDir, env, sysrootFlags(sdkRoot));
|
|
155
156
|
}
|
|
156
157
|
return objectPath;
|
|
157
158
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS SDK discovery.
|
|
3
|
+
*
|
|
4
|
+
* macOS has no `/usr/include`: every C standard header (`stdio.h`,
|
|
5
|
+
* `setjmp.h`, …) ships inside the SDK, and the linker's `libm`/`libSystem`
|
|
6
|
+
* stubs live there too. clang only searches the SDK when it has a sysroot,
|
|
7
|
+
* which comes from the `/usr/bin/clang` shim, an explicit `-isysroot`, or the
|
|
8
|
+
* `SDKROOT` environment variable.
|
|
9
|
+
*
|
|
10
|
+
* xbintsc invokes clang by absolute path (CI pins it to the oldest installed
|
|
11
|
+
* Xcode so released archives stay linkable by newer ones), which bypasses the
|
|
12
|
+
* shim. Without a sysroot that driver sees neither the SDK nor `/usr/include`,
|
|
13
|
+
* and the first runtime include fails with `'setjmp.h' file not found` — the
|
|
14
|
+
* header is irrelevant, any system header would do. The same gap makes the
|
|
15
|
+
* link fail later with `ld: library 'm' not found`.
|
|
16
|
+
*
|
|
17
|
+
* The SDK is therefore resolved once, with `xcrun`, and handed to clang as
|
|
18
|
+
* `-isysroot` on the command line rather than as `SDKROOT` in the environment:
|
|
19
|
+
* a self-hosted xbintsc binary runs the same driver, and its compiled
|
|
20
|
+
* `spawnSync` ignores `options.env`, so the variable would be lost exactly
|
|
21
|
+
* where the compiler rebuilds itself. An explicit `SDKROOT` in the environment
|
|
22
|
+
* always wins, so a developer can select an older SDK deliberately.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import type { CommandResult, Runner } from "./toolchain.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The macOS SDK `xcrun` reports, or `undefined` when the host is not macOS,
|
|
30
|
+
* the environment already names one, or no usable SDK can be resolved.
|
|
31
|
+
*
|
|
32
|
+
* A missing SDK is not an error here: the build still runs, and clang reports
|
|
33
|
+
* the real problem (or the driver never needed the SDK in the first place).
|
|
34
|
+
*/
|
|
35
|
+
export function macosSdkRoot(
|
|
36
|
+
runner: Runner,
|
|
37
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
38
|
+
platform: NodeJS.Platform = process.platform,
|
|
39
|
+
): string | undefined {
|
|
40
|
+
if (platform !== "darwin") return undefined;
|
|
41
|
+
if (env.SDKROOT) return undefined;
|
|
42
|
+
let result: CommandResult;
|
|
43
|
+
try {
|
|
44
|
+
result = runner.run("xcrun", ["--show-sdk-path"]);
|
|
45
|
+
} catch {
|
|
46
|
+
/* A host without xcrun keeps the previous behaviour: let clang decide. */
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (result.status !== 0) return undefined;
|
|
50
|
+
const sdk = result.stdout.trim();
|
|
51
|
+
return sdk.length > 0 && existsSync(sdk) ? sdk : undefined;
|
|
52
|
+
}
|
package/src/driver/options.ts
CHANGED
|
@@ -28,6 +28,12 @@ export interface BuildOptions {
|
|
|
28
28
|
readonly clang?: string;
|
|
29
29
|
/** Prefer a prebuilt `runtime/lib/<os>-<arch>/*.a` archive when present (default true). */
|
|
30
30
|
readonly preferPrebuilt?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Folders searched for the prebuilt `core`/`ext_<name>` archives, replacing
|
|
33
|
+
* the default (`runtime/lib/<os>-<arch>` plus the installed platform
|
|
34
|
+
* package). Mostly useful for tests and embedding tools.
|
|
35
|
+
*/
|
|
36
|
+
readonly runtimeLibDirs?: readonly string[];
|
|
31
37
|
readonly write?: boolean;
|
|
32
38
|
/** Application icon embedded into the binary (shorthand for `app.icon`). */
|
|
33
39
|
readonly icon?: string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oldest platform versions xbintsc supports, and the baseline every released
|
|
3
|
+
* artifact is built against.
|
|
4
|
+
*
|
|
5
|
+
* The prebuilt runtime archives (`core`, `ext_node`, `gui`, `media`) and the
|
|
6
|
+
* self-hosted compiler are built by CI with the **oldest toolchain each host
|
|
7
|
+
* still offers**, targeting the floors below. A newer Xcode/Visual Studio on a
|
|
8
|
+
* user's machine can link those older objects, but the reverse is what fails
|
|
9
|
+
* (`LNK2038`, `LNK2019`, `Undefined symbols`, `was built for newer macOS
|
|
10
|
+
* version`), which is why the direction matters.
|
|
11
|
+
*
|
|
12
|
+
* These values are the single source of truth for the archive build scripts,
|
|
13
|
+
* `xbintsc doctor` and `doc/requirements.md`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Deployment target for every macOS artifact (also the Apple Silicon floor). */
|
|
17
|
+
export const MACOS_MINIMUM_VERSION = "11.0";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Oldest Windows version the prebuilt objects and the compiler target.
|
|
21
|
+
*
|
|
22
|
+
* Windows 10 was the floor while the release jobs ran on `windows-2022`
|
|
23
|
+
* (VS 2022 / MSVC v143). Those images cannot assemble a release: their bundled
|
|
24
|
+
* `tar` has no built-in zstd, so `tar --zstd` hands compression to an external
|
|
25
|
+
* program the step waits on forever. The release jobs therefore run on
|
|
26
|
+
* `windows-2025`, whose toolset targets Windows 11, and the supported floor
|
|
27
|
+
* moved with them.
|
|
28
|
+
*/
|
|
29
|
+
export const WINDOWS_MINIMUM_VERSION = "11";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* glibc the release archives are built and tested against: the GitHub
|
|
33
|
+
* `ubuntu-24.04` image. Older distributions may link the npm package's
|
|
34
|
+
* archives, but only this floor is supported.
|
|
35
|
+
*/
|
|
36
|
+
export const LINUX_MINIMUM_GLIBC = "2.39";
|
|
37
|
+
|
|
38
|
+
/** Human-readable support floor for the host running the compiler. */
|
|
39
|
+
export function supportFloor(platform: NodeJS.Platform = process.platform): string {
|
|
40
|
+
switch (platform) {
|
|
41
|
+
case "darwin":
|
|
42
|
+
return `macOS ${MACOS_MINIMUM_VERSION}+`;
|
|
43
|
+
case "win32":
|
|
44
|
+
return `Windows ${WINDOWS_MINIMUM_VERSION}+`;
|
|
45
|
+
case "linux":
|
|
46
|
+
return `glibc ${LINUX_MINIMUM_GLIBC}+`;
|
|
47
|
+
default:
|
|
48
|
+
return `${platform} (unverified)`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Make a macOS archive build target {@link MACOS_MINIMUM_VERSION}.
|
|
54
|
+
*
|
|
55
|
+
* Clang's Darwin driver reads `MACOSX_DEPLOYMENT_TARGET`, and CMake reads it as
|
|
56
|
+
* the default for `CMAKE_OSX_DEPLOYMENT_TARGET`; setting both keeps the direct
|
|
57
|
+
* engine compiles, the vendored CMake builds and FFmpeg's configure on the same
|
|
58
|
+
* floor. An explicit value in the environment still wins, so a developer can
|
|
59
|
+
* target an even older macOS deliberately. No-op on the other hosts.
|
|
60
|
+
*/
|
|
61
|
+
export function applyMacosDeploymentTarget(platform: NodeJS.Platform = process.platform): void {
|
|
62
|
+
if (platform !== "darwin") return;
|
|
63
|
+
if (!process.env.MACOSX_DEPLOYMENT_TARGET) {
|
|
64
|
+
process.env.MACOSX_DEPLOYMENT_TARGET = MACOS_MINIMUM_VERSION;
|
|
65
|
+
}
|
|
66
|
+
if (!process.env.CMAKE_OSX_DEPLOYMENT_TARGET) {
|
|
67
|
+
process.env.CMAKE_OSX_DEPLOYMENT_TARGET = MACOS_MINIMUM_VERSION;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecting a prebuilt runtime archive that this machine's toolchain cannot
|
|
3
|
+
* link.
|
|
4
|
+
*
|
|
5
|
+
* The optional `@xbintsc/<os>-<arch>` package carries archives compiled on the
|
|
6
|
+
* release runner (`core.a`, `ext_node.a`, `gui.a`, `media.a`). Its Xcode
|
|
7
|
+
* Command Line Tools or Visual Studio C++ toolset can be **newer** than the
|
|
8
|
+
* one on the machine that runs the build: a linker can then reject the archive
|
|
9
|
+
* with unresolved symbols (`LNK2038`/`LNK2019`, `Undefined symbols`,
|
|
10
|
+
* `undefined reference`) or `was built for newer macOS version`.
|
|
11
|
+
*
|
|
12
|
+
* The driver answers that with one automatic retry that compiles the runtime
|
|
13
|
+
* from the sources shipped in the npm package. Its decision is based on the
|
|
14
|
+
* linker's own output rather than a version comparison, because an archive does
|
|
15
|
+
* not record the toolset that produced it portably: Mach-O objects carry
|
|
16
|
+
* `LC_BUILD_VERSION`, COFF objects do not carry an MSVC toolset version.
|
|
17
|
+
*
|
|
18
|
+
* `gui.a`/`media.a` are exempt: they bundle SDL3/FFmpeg/glslang, whose sources
|
|
19
|
+
* are not part of the npm package, so they cannot be rebuilt from it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { dirname, resolve } from "node:path";
|
|
23
|
+
|
|
24
|
+
const ARCHIVE_SUFFIXES = [".a", ".lib"] as const;
|
|
25
|
+
|
|
26
|
+
/** True when `candidate` is a static archive that sits in one of `dirs`. */
|
|
27
|
+
export function isPrebuiltArchivePath(candidate: string, dirs: readonly string[]): boolean {
|
|
28
|
+
if (!ARCHIVE_SUFFIXES.some((suffix) => candidate.endsWith(suffix))) return false;
|
|
29
|
+
const parent = dirname(resolve(candidate));
|
|
30
|
+
return dirs.some((dir) => resolve(dir) === parent);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Linker messages that mean "these objects were produced by a different
|
|
35
|
+
* toolchain/SDK". Kept deliberately narrow: a missing file, a bad flag or a
|
|
36
|
+
* permission error must not trigger the (slow) source rebuild.
|
|
37
|
+
*/
|
|
38
|
+
const MISMATCH_PATTERNS = [
|
|
39
|
+
/LNK2038/i, // mismatch detected for 'RuntimeLibrary'/'_MSC_VER'
|
|
40
|
+
/LNK20(?:01|19)/i, // unresolved external symbol
|
|
41
|
+
/undefined symbols?/i, // ld64
|
|
42
|
+
/undefined reference/i, // GNU/LLD
|
|
43
|
+
/symbol\(s\) not found/i, // ld64 summary line
|
|
44
|
+
/was built for newer (?:macOS|iOS|tvOS|watchOS|visionOS)/i, // ld64 warning
|
|
45
|
+
/but linking in object file built for/i, // ld64 deployment-target error
|
|
46
|
+
/has an incompatible (?:architecture|platform)/i,
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
/** Whether a failed link's stderr looks like a toolchain/archive mismatch. */
|
|
50
|
+
export function looksLikePrebuiltLinkFailure(stderr: string): boolean {
|
|
51
|
+
return MISMATCH_PATTERNS.some((pattern) => pattern.test(stderr));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** First non-empty line of a command's stderr, for a one-line build notice. */
|
|
55
|
+
export function firstFailureLine(stderr: string): string {
|
|
56
|
+
for (const line of stderr.split(/\r?\n/)) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (trimmed.length > 0) return trimmed.length > 160 ? `${trimmed.slice(0, 157)}...` : trimmed;
|
|
59
|
+
}
|
|
60
|
+
return "";
|
|
61
|
+
}
|