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.
Files changed (156) hide show
  1. package/README.md +8 -2
  2. package/README.zh-CN.md +9 -3
  3. package/dist/scripts/fetch-tarball.d.ts +28 -0
  4. package/dist/scripts/fetch-tarball.js +57 -0
  5. package/dist/scripts/fetch-tarball.js.map +1 -0
  6. package/dist/scripts/gui-build-options.d.ts +77 -0
  7. package/dist/scripts/gui-build-options.js +134 -0
  8. package/dist/scripts/gui-build-options.js.map +1 -0
  9. package/dist/src/cli/help.d.ts +1 -1
  10. package/dist/src/cli/hints.d.ts +6 -2
  11. package/dist/src/cli/hints.js +16 -2
  12. package/dist/src/cli/hints.js.map +1 -1
  13. package/dist/src/cli/main.js +9 -0
  14. package/dist/src/cli/main.js.map +1 -1
  15. package/dist/src/driver/app-artifacts.d.ts +2 -0
  16. package/dist/src/driver/app-artifacts.js +3 -3
  17. package/dist/src/driver/app-artifacts.js.map +1 -1
  18. package/dist/src/driver/compiler.js +147 -52
  19. package/dist/src/driver/compiler.js.map +1 -1
  20. package/dist/src/driver/icon.d.ts +1 -1
  21. package/dist/src/driver/icon.js +3 -3
  22. package/dist/src/driver/icon.js.map +1 -1
  23. package/dist/src/driver/macos-sdk.d.ts +32 -0
  24. package/dist/src/driver/macos-sdk.js +50 -0
  25. package/dist/src/driver/macos-sdk.js.map +1 -0
  26. package/dist/src/driver/options.d.ts +6 -0
  27. package/dist/src/driver/platform-support.d.ts +45 -0
  28. package/dist/src/driver/platform-support.js +66 -0
  29. package/dist/src/driver/platform-support.js.map +1 -0
  30. package/dist/src/driver/prebuilt.d.ts +26 -0
  31. package/dist/src/driver/prebuilt.js +58 -0
  32. package/dist/src/driver/prebuilt.js.map +1 -0
  33. package/dist/src/driver/runtime-objects.d.ts +11 -0
  34. package/dist/src/driver/runtime-objects.js +14 -5
  35. package/dist/src/driver/runtime-objects.js.map +1 -1
  36. package/dist/src/driver/toolchain-provider.d.ts +8 -0
  37. package/dist/src/driver/toolchain-provider.js +8 -0
  38. package/dist/src/driver/toolchain-provider.js.map +1 -1
  39. package/dist/src/driver/toolchain.d.ts +30 -1
  40. package/dist/src/driver/toolchain.js +88 -8
  41. package/dist/src/driver/toolchain.js.map +1 -1
  42. package/dist/src/driver/version.d.ts +1 -1
  43. package/dist/src/driver/version.js +1 -1
  44. package/dist/src/driver/version.js.map +1 -1
  45. package/dist/src/driver/win-entry.d.ts +1 -1
  46. package/dist/src/driver/win-entry.js +4 -4
  47. package/dist/src/driver/win-entry.js.map +1 -1
  48. package/dist/src/extensions/gui/html.d.ts +10 -7
  49. package/dist/src/extensions/gui/html.js +53 -29
  50. package/dist/src/extensions/gui/html.js.map +1 -1
  51. package/dist/src/extensions/node/child_process/index.d.ts +4 -3
  52. package/dist/src/extensions/node/child_process/index.js +4 -3
  53. package/dist/src/extensions/node/child_process/index.js.map +1 -1
  54. package/dist/tests/cli/hints.test.js +9 -1
  55. package/dist/tests/cli/hints.test.js.map +1 -1
  56. package/dist/tests/cli/main.test.js +9 -0
  57. package/dist/tests/cli/main.test.js.map +1 -1
  58. package/dist/tests/driver/compiler.test.js +107 -0
  59. package/dist/tests/driver/compiler.test.js.map +1 -1
  60. package/dist/tests/driver/cxx-compile-flags.test.d.ts +1 -0
  61. package/dist/tests/driver/cxx-compile-flags.test.js +24 -0
  62. package/dist/tests/driver/cxx-compile-flags.test.js.map +1 -0
  63. package/dist/tests/driver/macos-sdk.test.d.ts +1 -0
  64. package/dist/tests/driver/macos-sdk.test.js +83 -0
  65. package/dist/tests/driver/macos-sdk.test.js.map +1 -0
  66. package/dist/tests/driver/platform-support.test.d.ts +1 -0
  67. package/dist/tests/driver/platform-support.test.js +41 -0
  68. package/dist/tests/driver/platform-support.test.js.map +1 -0
  69. package/dist/tests/driver/prebuilt.test.d.ts +1 -0
  70. package/dist/tests/driver/prebuilt.test.js +44 -0
  71. package/dist/tests/driver/prebuilt.test.js.map +1 -0
  72. package/dist/tests/driver/toolchain-provider.test.js +24 -0
  73. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  74. package/dist/tests/driver/toolchain.test.js +46 -2
  75. package/dist/tests/driver/toolchain.test.js.map +1 -1
  76. package/dist/tests/e2e/async-await.test.js +4 -2
  77. package/dist/tests/e2e/async-await.test.js.map +1 -1
  78. package/dist/tests/e2e/compile-node.test.js +21 -0
  79. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  80. package/dist/tests/e2e/fetch.test.js +34 -6
  81. package/dist/tests/e2e/fetch.test.js.map +1 -1
  82. package/dist/tests/e2e/gui-example-pelican3d.test.d.ts +10 -0
  83. package/dist/tests/e2e/gui-example-pelican3d.test.js +94 -0
  84. package/dist/tests/e2e/gui-example-pelican3d.test.js.map +1 -0
  85. package/dist/tests/e2e/gui-script.test.js +48 -0
  86. package/dist/tests/e2e/gui-script.test.js.map +1 -1
  87. package/dist/tests/e2e/unhandled-rejection.test.d.ts +14 -0
  88. package/dist/tests/e2e/unhandled-rejection.test.js +107 -0
  89. package/dist/tests/e2e/unhandled-rejection.test.js.map +1 -0
  90. package/dist/tests/extensions/gui.test.js +44 -0
  91. package/dist/tests/extensions/gui.test.js.map +1 -1
  92. package/dist/tests/scripts/fetch-tarball.test.d.ts +1 -0
  93. package/dist/tests/scripts/fetch-tarball.test.js +70 -0
  94. package/dist/tests/scripts/fetch-tarball.test.js.map +1 -0
  95. package/doc/ai/cli.md +6 -0
  96. package/doc/ai/language-support.md +1 -0
  97. package/doc/ai/troubleshooting.md +32 -2
  98. package/doc/ai/zh-CN/cli.md +5 -0
  99. package/doc/ai/zh-CN/language-support.md +1 -0
  100. package/doc/ai/zh-CN/troubleshooting.md +27 -2
  101. package/doc/gui/build.md +7 -3
  102. package/doc/gui/dom.md +3 -1
  103. package/doc/gui/webgl.md +4 -0
  104. package/doc/gui-scripts.md +33 -17
  105. package/doc/gui.md +17 -1
  106. package/doc/implemented/async.md +1 -0
  107. package/doc/implemented/modules.md +9 -6
  108. package/doc/implemented/node/child-process.md +12 -4
  109. package/doc/implemented/runtime-core.md +1 -1
  110. package/doc/npm-package.md +23 -2
  111. package/doc/post/make-js-great-again.md +30 -0
  112. package/doc/requirements.md +36 -3
  113. package/doc/unimplemented/deviations.md +1 -0
  114. package/doc/zh-CN/gui/build.md +4 -2
  115. package/doc/zh-CN/gui/dom.md +3 -1
  116. package/doc/zh-CN/gui/webgl.md +1 -0
  117. package/doc/zh-CN/gui-scripts.md +23 -11
  118. package/doc/zh-CN/gui.md +11 -1
  119. package/doc/zh-CN/implemented/async.md +1 -0
  120. package/doc/zh-CN/implemented/modules.md +8 -4
  121. package/doc/zh-CN/implemented/node/child-process.md +10 -3
  122. package/doc/zh-CN/implemented/runtime-core.md +1 -1
  123. package/doc/zh-CN/npm-package.md +18 -1
  124. package/doc/zh-CN/post/make-js-great-again.md +32 -0
  125. package/doc/zh-CN/requirements.md +30 -2
  126. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  127. package/package.json +7 -7
  128. package/runtime/ext_node/child_process/child_process.c +149 -7
  129. package/runtime/rt.h +4 -0
  130. package/runtime/xt_async.c +4 -0
  131. package/runtime/xt_generator.c +2 -0
  132. package/runtime/xt_loop.c +8 -4
  133. package/runtime/xt_promise.c +103 -0
  134. package/scripts/build-gui.ts +17 -5
  135. package/scripts/build-media.ts +89 -10
  136. package/scripts/build-runtime.ts +7 -2
  137. package/scripts/fetch-tarball.ts +71 -0
  138. package/scripts/gui-build-cache.ts +28 -0
  139. package/scripts/gui-build-options.ts +26 -2
  140. package/scripts/package-release.ts +8 -0
  141. package/src/cli/hints.ts +19 -2
  142. package/src/cli/main.ts +11 -0
  143. package/src/driver/app-artifacts.ts +5 -3
  144. package/src/driver/compiler.ts +167 -56
  145. package/src/driver/icon.ts +3 -2
  146. package/src/driver/macos-sdk.ts +52 -0
  147. package/src/driver/options.ts +6 -0
  148. package/src/driver/platform-support.ts +69 -0
  149. package/src/driver/prebuilt.ts +61 -0
  150. package/src/driver/runtime-objects.ts +26 -5
  151. package/src/driver/toolchain-provider.ts +11 -0
  152. package/src/driver/toolchain.ts +105 -9
  153. package/src/driver/version.ts +1 -1
  154. package/src/driver/win-entry.ts +4 -3
  155. package/src/extensions/gui/html.ts +61 -31
  156. package/src/extensions/node/child_process/index.ts +4 -3
@@ -12,7 +12,7 @@ import type { ExtensionRegistry } from "../extensions/registry.js";
12
12
  import type { ResolvedLinkFeature } from "./link-features.js";
13
13
  import { hashParts } from "./cache.js";
14
14
  import { findRuntimeLibrary } from "./runtime-lib.js";
15
- import { compileC, optimizationFlags, RUNTIME_CFLAGS, type PgoMode, type Runner } from "./toolchain.js";
15
+ import { compileC, optimizationFlags, RUNTIME_CFLAGS, sysrootFlags, type PgoMode, type Runner } from "./toolchain.js";
16
16
  import { COMPILER_VERSION } from "./version.js";
17
17
 
18
18
  /** Core runtime translation units (each compiled and cached independently). */
@@ -51,6 +51,12 @@ export interface RuntimeObjects {
51
51
  readonly featureObjects: readonly string[];
52
52
  /** Pre-built C++/Rust objects and archives contributed by native extensions. */
53
53
  readonly nativeObjects: readonly string[];
54
+ /**
55
+ * The prebuilt `core`/`ext_<name>` archives this link used. Empty when the
56
+ * runtime was compiled from source. The driver retries a failed link without
57
+ * them, so it has to know whether the first attempt used any.
58
+ */
59
+ readonly prebuiltArchives: readonly string[];
54
60
  }
55
61
 
56
62
  export interface RuntimeObjectsOptions {
@@ -61,11 +67,16 @@ export interface RuntimeObjectsOptions {
61
67
  readonly registry: ExtensionRegistry;
62
68
  readonly preferPrebuilt: boolean;
63
69
  readonly env: Record<string, string>;
70
+ /** macOS SDK for `-isysroot`; part of the object cache key. */
71
+ readonly sdkRoot?: string;
64
72
  readonly lto: boolean;
65
73
  readonly pgo: PgoMode | undefined;
66
74
  readonly pgoDir: string;
67
75
  /** Optional link features selected for this program. */
68
76
  readonly features?: readonly ResolvedLinkFeature[];
77
+ /** Override the folders searched for prebuilt archives (defaults to the
78
+ * runtime's `lib/<os>-<arch>` plus the installed platform package). */
79
+ readonly runtimeLibDirs?: readonly string[];
69
80
  }
70
81
 
71
82
  /**
@@ -79,6 +90,9 @@ export interface RuntimeObjectsOptions {
79
90
  export function ensureRuntimeObjects(options: RuntimeObjectsOptions): RuntimeObjects {
80
91
  const { runner, clang, runtimeDir, cacheDir, registry, preferPrebuilt, env, lto, pgo, pgoDir } = options;
81
92
  const optimization = optimizationFlags(lto, pgo, pgoDir);
93
+ /* The sysroot joins the extra flags (and therefore the cache key): objects
94
+ compiled against a different SDK are a different artifact. */
95
+ const compileFlags = [...optimization, ...sysrootFlags(options.sdkRoot)];
82
96
  /* Runtime objects depend on the shared headers (rt.h/rt_internal.h/...) and
83
97
  the `#include`d implementation fragments (`.inc`), so any change to them
84
98
  must invalidate every cached object. Hash them all. */
@@ -99,7 +113,7 @@ export function ensureRuntimeObjects(options: RuntimeObjectsOptions): RuntimeObj
99
113
  "runtime",
100
114
  COMPILER_VERSION,
101
115
  RUNTIME_CFLAGS.join(" "),
102
- optimization.join(" "),
116
+ compileFlags.join(" "),
103
117
  tag,
104
118
  headerStamp,
105
119
  body,
@@ -108,24 +122,30 @@ export function ensureRuntimeObjects(options: RuntimeObjectsOptions): RuntimeObj
108
122
  ]);
109
123
  const objectPath = join(cacheDir, `${tag}-${key}.o`);
110
124
  if (!existsSync(objectPath)) {
111
- compileC(runner, clang, sourcePath, objectPath, runtimeDir, env, optimization);
125
+ compileC(runner, clang, sourcePath, objectPath, runtimeDir, env, compileFlags);
112
126
  }
113
127
  return objectPath;
114
128
  };
115
129
 
116
- const coreArchive = preferPrebuilt ? findRuntimeLibrary("core") : undefined;
130
+ const coreArchive = preferPrebuilt ? findRuntimeLibrary("core", options.runtimeLibDirs) : undefined;
117
131
  const runtimeObjects = coreArchive
118
132
  ? [coreArchive]
119
133
  : RUNTIME_SOURCES.map((source) => compileOne(join(runtimeDir, source), basename(source, ".c")));
120
134
 
135
+ /** Prebuilt archives this link uses; the driver retries without them. */
136
+ const prebuiltArchives: string[] = coreArchive ? [coreArchive] : [];
137
+
121
138
  const extensionObjects: string[] = [];
122
139
  const seen = new Set<string>();
123
140
  for (const extension of registry.all()) {
124
141
  const sources = extension.runtimeSources ? extension.runtimeSources() : [];
125
142
  if (sources.length === 0) continue;
126
- const archive = preferPrebuilt ? findRuntimeLibrary(`ext_${extension.name}`) : undefined;
143
+ const archive = preferPrebuilt
144
+ ? findRuntimeLibrary(`ext_${extension.name}`, options.runtimeLibDirs)
145
+ : undefined;
127
146
  if (archive) {
128
147
  extensionObjects.push(archive);
148
+ prebuiltArchives.push(archive);
129
149
  continue;
130
150
  }
131
151
  for (const sourcePath of sources) {
@@ -156,5 +176,6 @@ export function ensureRuntimeObjects(options: RuntimeObjectsOptions): RuntimeObj
156
176
  extensionObjects,
157
177
  featureObjects: [...new Set(featureObjects)],
158
178
  nativeObjects: [...new Set(registry.nativeObjects())],
179
+ prebuiltArchives,
159
180
  };
160
181
  }
@@ -22,11 +22,17 @@
22
22
  * skipped (or rejected, when named explicitly) instead of producing objects
23
23
  * the linker cannot mix.
24
24
  *
25
+ * On macOS the resolved driver also carries the SDK as `sdkRoot`, which the
26
+ * compile and link steps pass as `-isysroot` (see `macos-sdk.ts`): an Apple
27
+ * clang invoked by absolute path does not infer the SDK by itself, and without
28
+ * one not even `<stdio.h>` — nor the linker's `libm` stub — is found.
29
+ *
25
30
  * Extra linker arguments (for example `-fuse-ld=lld`) can be supplied through
26
31
  * `xbintsc_LINKER_ARGS`.
27
32
  */
28
33
 
29
34
  import { locateClang, realRunner, type ClangVersion, type Runner } from "./toolchain.js";
35
+ import { macosSdkRoot } from "./macos-sdk.js";
30
36
 
31
37
  export type ToolchainSource = "env" | "system";
32
38
 
@@ -42,6 +48,9 @@ export interface ResolvedToolchain {
42
48
  /** Extra environment for the toolchain subprocesses, merged over the current
43
49
  * environment. */
44
50
  readonly env: Record<string, string>;
51
+ /** macOS SDK to pass as `-isysroot`; lost off macOS or when `SDKROOT` is
52
+ * already set. See `macos-sdk.ts`. */
53
+ readonly sdkRoot?: string;
45
54
  }
46
55
 
47
56
  function linkerArgs(fallback: readonly string[]): string[] {
@@ -53,11 +62,13 @@ function linkerArgs(fallback: readonly string[]): string[] {
53
62
  /** Resolve the toolchain that `build` should use. See the module docblock. */
54
63
  export function resolveToolchain(runner: Runner = realRunner): ResolvedToolchain {
55
64
  const location = locateClang(runner);
65
+ const sdkRoot = macosSdkRoot(runner);
56
66
  return {
57
67
  clang: location.command,
58
68
  source: location.source,
59
69
  ...(location.version ? { version: location.version } : {}),
60
70
  linkerArgs: linkerArgs([]),
61
71
  env: {},
72
+ ...(sdkRoot === undefined ? {} : { sdkRoot }),
62
73
  };
63
74
  }
@@ -144,6 +144,75 @@ export function probeClang(
144
144
  }
145
145
  }
146
146
 
147
+ /**
148
+ * Release years that name their install directory after the year, mapped to the
149
+ * major version each release actually ships (VS 2022 is version 17).
150
+ */
151
+ const VISUAL_STUDIO_YEAR_MAJOR: Readonly<Record<string, number>> = {
152
+ "2015": 14,
153
+ "2017": 15,
154
+ "2019": 16,
155
+ "2022": 17,
156
+ };
157
+
158
+ /**
159
+ * Sort key for a directory under `%ProgramFiles%\Microsoft Visual Studio`.
160
+ * Releases up to 2022 use year names ("2022"); Visual Studio 2026 (version 18)
161
+ * and later use the plain major version ("18").
162
+ */
163
+ function visualStudioMajor(directoryName: string): number {
164
+ const known = VISUAL_STUDIO_YEAR_MAJOR[directoryName];
165
+ if (known !== undefined) return known;
166
+ return /^\d+$/.test(directoryName) ? Number(directoryName) : -1;
167
+ }
168
+
169
+ /** Immediate subdirectory names, or `[]` when the directory cannot be read. */
170
+ function subdirectories(directory: string): string[] {
171
+ try {
172
+ return readdirSync(directory, { withFileTypes: true })
173
+ .filter((entry) => entry.isDirectory())
174
+ .map((entry) => entry.name);
175
+ } catch {
176
+ return [];
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Preference order when one release has several installs; channel names not
182
+ * listed here (for example `Insiders`) follow alphabetically.
183
+ */
184
+ const VISUAL_STUDIO_EDITIONS: readonly string[] = ["Community", "Professional", "Enterprise", "BuildTools"];
185
+
186
+ function editionRank(edition: string): number {
187
+ const index = VISUAL_STUDIO_EDITIONS.indexOf(edition);
188
+ return index === -1 ? VISUAL_STUDIO_EDITIONS.length : index;
189
+ }
190
+
191
+ /**
192
+ * The `VC\Tools\Llvm` directory of every Visual Studio installation under one
193
+ * `Microsoft Visual Studio` root, newest release first.
194
+ *
195
+ * Both layouts are discovered by enumerating directories — `<year>\<edition>`
196
+ * for 2019/2022 (for example `2022\BuildTools`) and `<major>\<channel>` for
197
+ * Visual Studio 2026 and later (for example `18\Insiders`) — so a future
198
+ * release needs no code change and a non-default channel still works.
199
+ */
200
+ export function visualStudioLlvmDirs(visualStudioRoot: string): string[] {
201
+ const versions = subdirectories(visualStudioRoot)
202
+ .filter((name) => visualStudioMajor(name) >= 0)
203
+ .sort((a, b) => visualStudioMajor(b) - visualStudioMajor(a));
204
+ const dirs: string[] = [];
205
+ for (const version of versions) {
206
+ const editions = subdirectories(join(visualStudioRoot, version)).sort(
207
+ (a, b) => editionRank(a) - editionRank(b) || a.localeCompare(b),
208
+ );
209
+ for (const edition of editions) {
210
+ dirs.push(join(visualStudioRoot, version, edition, "VC", "Tools", "Llvm"));
211
+ }
212
+ }
213
+ return dirs;
214
+ }
215
+
147
216
  /**
148
217
  * Visual Studio ships its own LLVM build (`VC\Tools\Llvm\x64\bin\clang.exe`).
149
218
  * It is a perfectly good driver but is not on `PATH` in a plain shell, so probe
@@ -152,17 +221,19 @@ export function probeClang(
152
221
  */
153
222
  function visualStudioClangCandidates(): string[] {
154
223
  if (process.platform !== "win32") return [];
155
- const roots = [process.env.ProgramFiles, process.env["ProgramFiles(x86)"], process.env.ProgramW6432]
156
- .filter((root): root is string => Boolean(root));
224
+ const roots = [
225
+ ...new Set(
226
+ [process.env.ProgramFiles, process.env["ProgramFiles(x86)"], process.env.ProgramW6432].filter(
227
+ (root): root is string => Boolean(root),
228
+ ),
229
+ ),
230
+ ];
157
231
  const arch = process.arch === "arm64" ? "ARM64" : "x64";
158
232
  const candidates: string[] = [];
159
233
  for (const root of roots) {
160
- for (const year of ["2022", "2019"]) {
161
- for (const edition of ["Community", "Professional", "Enterprise", "BuildTools"]) {
162
- const llvm = join(root, "Microsoft Visual Studio", year, edition, "VC", "Tools", "Llvm");
163
- candidates.push(join(llvm, arch, "bin", "clang.exe"));
164
- candidates.push(join(llvm, "bin", "clang.exe"));
165
- }
234
+ for (const llvm of visualStudioLlvmDirs(join(root, "Microsoft Visual Studio"))) {
235
+ candidates.push(join(llvm, arch, "bin", "clang.exe"));
236
+ candidates.push(join(llvm, "bin", "clang.exe"));
166
237
  }
167
238
  candidates.push(join(root, "LLVM", "bin", "clang.exe"));
168
239
  }
@@ -299,12 +370,25 @@ export function optimizationFlags(
299
370
  return flags;
300
371
  }
301
372
 
373
+ /**
374
+ * `-isysroot` for a resolved macOS SDK, or nothing off macOS.
375
+ *
376
+ * The sysroot travels on the command line and not as `SDKROOT` in the
377
+ * environment because the self-hosted compiler's `spawnSync` ignores
378
+ * `options.env`; flags survive that. See `macos-sdk.ts`.
379
+ */
380
+ export function sysrootFlags(sdkRoot: string | undefined): string[] {
381
+ return sdkRoot === undefined ? [] : ["-isysroot", sdkRoot];
382
+ }
383
+
302
384
  export interface CompileIrOptions {
303
385
  readonly clang: string;
304
386
  readonly irPath: string;
305
387
  readonly objectPath: string;
306
388
  readonly optimize: string;
307
389
  readonly env?: Record<string, string>;
390
+ /** macOS SDK for `-isysroot`; see {@link sysrootFlags}. */
391
+ readonly sdkRoot?: string;
308
392
  /** Emit debug info (`-g`) and keep frame pointers for crash backtraces. */
309
393
  readonly debug?: boolean;
310
394
  /** Compile to LTO bitcode and let the linker optimize across units. */
@@ -332,6 +416,7 @@ export function compileIr(runner: Runner, options: CompileIrOptions): void {
332
416
  ...SECTION_FLAGS,
333
417
  ...debugFlags,
334
418
  ...profileFlags,
419
+ ...sysrootFlags(options.sdkRoot),
335
420
  "-c",
336
421
  options.irPath,
337
422
  "-o",
@@ -349,6 +434,9 @@ export interface LinkOptions {
349
434
  readonly linkerFlags: readonly string[];
350
435
  readonly optimize: string;
351
436
  readonly env?: Record<string, string>;
437
+ /** macOS SDK for `-isysroot`; the linker's stubs (`libm`, `libSystem`) come
438
+ * from it, so a link without one fails with `library 'm' not found`. */
439
+ readonly sdkRoot?: string;
352
440
  readonly lto?: boolean;
353
441
  readonly pgo?: PgoMode;
354
442
  readonly pgoDir?: string;
@@ -361,7 +449,13 @@ function deadStripFlag(): string[] {
361
449
  return ["-Wl,--gc-sections"];
362
450
  }
363
451
 
364
- export function link(runner: Runner, options: LinkOptions): void {
452
+ /**
453
+ * Link the program and return the command result. A successful link can still
454
+ * carry a warning (for example ld64's "object file was built for newer macOS
455
+ * version"); the driver inspects `stderr` to decide whether a prebuilt archive
456
+ * should be rebuilt from source.
457
+ */
458
+ export function link(runner: Runner, options: LinkOptions): CommandResult {
365
459
  const profileFlags = optimizationFlags(options.lto, options.pgo, options.pgoDir);
366
460
  const args = [
367
461
  `-O${options.optimize}`,
@@ -370,6 +464,7 @@ export function link(runner: Runner, options: LinkOptions): void {
370
464
  * understands both ELF/Mach-O and COFF; without it MSVC link.exe rejects
371
465
  * the bitcode object on Windows. */
372
466
  ...(options.lto ? ["-fuse-ld=lld"] : []),
467
+ ...sysrootFlags(options.sdkRoot),
373
468
  ...options.objectPaths,
374
469
  "-o",
375
470
  options.outputPath,
@@ -378,6 +473,7 @@ export function link(runner: Runner, options: LinkOptions): void {
378
473
  ];
379
474
  const result = runner.run(options.clang, args, { env: options.env });
380
475
  if (result.status !== 0) throw new ToolchainError(`${options.clang} link`, result.status, result.stderr);
476
+ return result;
381
477
  }
382
478
 
383
479
  /**
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.52";
12
+ export const COMPILER_VERSION = "0.6.9";
@@ -15,7 +15,7 @@
15
15
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
  import { hashParts } from "./cache.js";
18
- import { compileC, type Runner } from "./toolchain.js";
18
+ import { compileC, sysrootFlags, type Runner } from "./toolchain.js";
19
19
 
20
20
  /** The bridge source. `main` is the entry point the runtime emits. */
21
21
  export const WINDOWS_ENTRY_SOURCE = `/* Generated by xbintsc - Windows GUI subsystem entry point. Do not edit. */
@@ -46,14 +46,15 @@ export function ensureWindowsEntryObject(
46
46
  clang: string,
47
47
  cacheDir: string,
48
48
  env?: Record<string, string>,
49
+ sdkRoot?: string,
49
50
  ): string {
50
- const key = hashParts(["win-gui-entry", process.platform, clang, WINDOWS_ENTRY_SOURCE]);
51
+ const key = hashParts(["win-gui-entry", process.platform, clang, sdkRoot ?? "", WINDOWS_ENTRY_SOURCE]);
51
52
  const sourcePath = join(cacheDir, `winentry-${key}.c`);
52
53
  const objectPath = join(cacheDir, `winentry-${key}.o`);
53
54
  if (!existsSync(objectPath)) {
54
55
  mkdirSync(cacheDir, { recursive: true });
55
56
  writeFileSync(sourcePath, WINDOWS_ENTRY_SOURCE);
56
- compileC(runner, clang, sourcePath, objectPath, cacheDir, env);
57
+ compileC(runner, clang, sourcePath, objectPath, cacheDir, env, sysrootFlags(sdkRoot));
57
58
  }
58
59
  return objectPath;
59
60
  }
@@ -12,13 +12,16 @@
12
12
  * This is how xbintsc supports `<script>` without a JavaScript engine: the body
13
13
  * is ordinary TypeScript compiled by the existing front end, ahead of time.
14
14
  *
15
- * Inline `<script lang="ts">body</script>` bodies are wrapped directly. External
16
- * `<script src="./app.ts">` files are read, their top-level `import`/`export
17
- * … from` statements are hoisted to the generated module (with relative
18
- * specifiers rewritten so they resolve from the HTML file), and the remaining
19
- * statements are wrapped in the script function. All scripts run in document
20
- * order after parsing (there is no "eager" execution point), i.e. every script
21
- * is effectively `defer`red.
15
+ * Both forms are compiled the same way: an inline
16
+ * `<script lang="ts">body</script>` body and an external `<script src="./app.ts">`
17
+ * file are parsed as a module, their top-level `import`/`export … from`
18
+ * statements are hoisted to the generated module (with relative specifiers
19
+ * rewritten so they resolve from the HTML file), and the remaining statements
20
+ * are wrapped in the script function. An inline body therefore resolves imports
21
+ * relative to the page that contains it, exactly like `<script type="module">`
22
+ * in a browser — including a `node:`/bare specifier, which links like any other
23
+ * module import. All scripts run in document order after parsing (there is no
24
+ * "eager" execution point), i.e. every script is effectively `defer`red.
22
25
  */
23
26
 
24
27
  import { existsSync, readFileSync } from "node:fs";
@@ -161,12 +164,18 @@ function deferredPrelude(alias: string, declaration: ImportDeclaration): string[
161
164
  return lines;
162
165
  }
163
166
 
164
- /** Split an external script module into hoisted imports and a body. */
165
- function compileModuleSource(htmlPath: string, scriptPath: string, scriptIndex: number): CompiledModule {
167
+ /** Split a script module into hoisted imports and a body. */
168
+ function compileModuleSource(
169
+ htmlPath: string,
170
+ scriptPath: string,
171
+ scriptSource: string,
172
+ scriptIndex: number,
173
+ ): CompiledModule {
166
174
  /* Mask the normalized text: the parser indexes the `SourceFile` text, where
167
175
  * `\r\n` already became `\n`, so blanking statement spans in the raw file
168
- * would remove the wrong characters on a CRLF checkout. */
169
- const source = new SourceFile(scriptPath, readFileSync(scriptPath, "utf8")).text;
176
+ * would remove the wrong characters on a CRLF checkout. An inline body is
177
+ * already normalized (the page was), so this is the identity for it. */
178
+ const source = new SourceFile(scriptPath, scriptSource).text;
170
179
  const bag = new DiagnosticBag();
171
180
  const parsed = new Parser(new SourceFile(scriptPath, source), bag).parseSourceFile();
172
181
  const chars = source.split("");
@@ -272,6 +281,35 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
272
281
  const importedText = new Set<string>();
273
282
  const bound = new Set<string>();
274
283
 
284
+ /* Every script's hoisted imports live at module scope, so they are collected
285
+ * here and placed in front of the script functions. `element` and `hint` only
286
+ * shape the error for an eager binding claimed by two scripts. */
287
+ const hoistImports = (compiled: CompiledModule, element: string, hint: string): void => {
288
+ for (const text of compiled.imports) {
289
+ if (!importedText.has(text)) {
290
+ importedText.add(text);
291
+ imports.push(text);
292
+ }
293
+ }
294
+ lazyImports.push(...compiled.lazyImports);
295
+ for (const name of compiled.localNames) {
296
+ if (bound.has(name)) {
297
+ throw new Error(
298
+ `gui: ${element} imports '${name}', already imported by another <script>; ${hint}`,
299
+ );
300
+ }
301
+ bound.add(name);
302
+ }
303
+ };
304
+ const register = (compiled: CompiledModule, file?: string): string => {
305
+ /* Hash the wrapped body, prelude included: the engine keys the registry by
306
+ * id, so two scripts with the same statements but different deferred
307
+ * imports must not collapse into one entry. */
308
+ const id = hashString([...compiled.prelude, compiled.body].join("\n"));
309
+ scripts.push({ id, body: compiled.body, prelude: compiled.prelude, ...(file ? { file } : {}) });
310
+ return id;
311
+ };
312
+
275
313
  /* The engine's cascade only reads `<style>` elements, so a linked stylesheet
276
314
  * would be dropped at runtime. Inline it (and its `@import`s) now. */
277
315
  const styles = inlineStylesheets(path, page);
@@ -284,32 +322,24 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
284
322
  if (!existsSync(scriptPath)) {
285
323
  throw new Error(`gui: <script src="${src}"> not found (looked for ${scriptPath})`);
286
324
  }
287
- const compiled = compileModuleSource(path, scriptPath, scripts.length);
288
- for (const text of compiled.imports) {
289
- if (!importedText.has(text)) {
290
- importedText.add(text);
291
- imports.push(text);
292
- }
293
- }
294
- lazyImports.push(...compiled.lazyImports);
295
- for (const name of compiled.localNames) {
296
- if (bound.has(name)) {
297
- throw new Error(
298
- `gui: <script src="${src}"> imports '${name}', already imported by another <script src>; alias it in ${scriptPath}`,
299
- );
300
- }
301
- bound.add(name);
302
- }
303
- const id = hashString([...compiled.prelude, compiled.body].join("\n"));
304
- scripts.push({ id, body: compiled.body, prelude: compiled.prelude, file: scriptPath });
325
+ const compiled = compileModuleSource(path, scriptPath, readFileSync(scriptPath, "utf8"), scripts.length);
326
+ hoistImports(compiled, `<script src="${src}">`, `alias it in ${scriptPath}`);
327
+ const id = register(compiled, scriptPath);
305
328
  return `<script data-xt-id="${id}"></script>`;
306
329
  }
307
330
 
308
331
  /* A `src` URL, or a data block: leave the element untouched. */
309
332
  if (src !== undefined) return match;
310
333
  if (inlineBody.trim() === "") return match;
311
- const id = hashString(inlineBody);
312
- scripts.push({ id, body: inlineBody, prelude: [] });
334
+ /* An inline body is compiled exactly like a `<script src>` file, so an
335
+ * `import` links the same way in both: a bare or `node:` specifier is
336
+ * hoisted to the generated module, a relative one is deferred, and both
337
+ * resolve from the HTML file as a browser module script would. Wrapping the
338
+ * body verbatim used to leave an inline `import` inside the script function,
339
+ * where codegen cannot link its binding (TS5001). */
340
+ const compiled = compileModuleSource(path, path, inlineBody, scripts.length);
341
+ hoistImports(compiled, "inline <script>", "alias it");
342
+ const id = register(compiled);
313
343
  return `<script data-xt-id="${id}"></script>`;
314
344
  });
315
345
 
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Node's `child_process` module: `spawnSync(command, args, options)`, backed by
3
3
  * `runtime/ext_node/child_process`. Captures stdout/stderr as UTF-8 strings,
4
- * honours `options.cwd`, and supports `options.stdio: "inherit"` (hand the
5
- * child the parent's stdout/stderr) — which is all the compiler's toolchain
6
- * wrapper and `xbintsc run` need.
4
+ * honours `options.cwd` and `options.env` (replace semantics, like Node), and
5
+ * supports `options.stdio: "inherit"` (hand the child the parent's
6
+ * stdout/stderr) — which is all the compiler's toolchain wrapper and
7
+ * `xbintsc run` need.
7
8
  */
8
9
 
9
10
  import type { NodeModule } from "../module.js";