xbintsc 0.5.45 → 0.5.52

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 (286) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +5 -4
  3. package/README.zh-CN.md +4 -3
  4. package/dist/src/cli/extensions.d.ts +24 -0
  5. package/dist/src/cli/extensions.js +62 -0
  6. package/dist/src/cli/extensions.js.map +1 -0
  7. package/dist/src/cli/help.d.ts +1 -1
  8. package/dist/src/cli/help.js +3 -1
  9. package/dist/src/cli/help.js.map +1 -1
  10. package/dist/src/cli/hints.js +8 -3
  11. package/dist/src/cli/hints.js.map +1 -1
  12. package/dist/src/cli/main.js +21 -28
  13. package/dist/src/cli/main.js.map +1 -1
  14. package/dist/src/diagnostics/diagnostic.d.ts +3 -1
  15. package/dist/src/diagnostics/diagnostic.js +2 -0
  16. package/dist/src/diagnostics/diagnostic.js.map +1 -1
  17. package/dist/src/driver/build-key.d.ts +2 -0
  18. package/dist/src/driver/build-key.js +1 -0
  19. package/dist/src/driver/build-key.js.map +1 -1
  20. package/dist/src/driver/compiler.js +61 -8
  21. package/dist/src/driver/compiler.js.map +1 -1
  22. package/dist/src/driver/config.d.ts +4 -0
  23. package/dist/src/driver/config.js +2 -0
  24. package/dist/src/driver/config.js.map +1 -1
  25. package/dist/src/driver/link-features.d.ts +37 -0
  26. package/dist/src/driver/link-features.js +123 -0
  27. package/dist/src/driver/link-features.js.map +1 -0
  28. package/dist/src/driver/options.d.ts +10 -0
  29. package/dist/src/driver/runtime-objects.d.ts +5 -0
  30. package/dist/src/driver/runtime-objects.js +20 -1
  31. package/dist/src/driver/runtime-objects.js.map +1 -1
  32. package/dist/src/driver/usage.d.ts +23 -0
  33. package/dist/src/driver/usage.js +353 -0
  34. package/dist/src/driver/usage.js.map +1 -0
  35. package/dist/src/driver/version.d.ts +1 -1
  36. package/dist/src/driver/version.js +1 -1
  37. package/dist/src/extensions/catalog.js +1 -2
  38. package/dist/src/extensions/catalog.js.map +1 -1
  39. package/dist/src/extensions/gui/features.d.ts +34 -0
  40. package/dist/src/extensions/gui/features.js +165 -0
  41. package/dist/src/extensions/gui/features.js.map +1 -0
  42. package/dist/src/extensions/gui/index.js +10 -4
  43. package/dist/src/extensions/gui/index.js.map +1 -1
  44. package/dist/src/extensions/link-feature.d.ts +70 -0
  45. package/dist/src/extensions/link-feature.js +18 -0
  46. package/dist/src/extensions/link-feature.js.map +1 -0
  47. package/dist/src/extensions/registry.d.ts +24 -4
  48. package/dist/src/extensions/registry.js +26 -3
  49. package/dist/src/extensions/registry.js.map +1 -1
  50. package/dist/src/index.d.ts +3 -2
  51. package/dist/src/index.js +0 -1
  52. package/dist/src/index.js.map +1 -1
  53. package/dist/tests/cli/hints.test.js +5 -7
  54. package/dist/tests/cli/hints.test.js.map +1 -1
  55. package/dist/tests/driver/link-features.test.js +105 -0
  56. package/dist/tests/driver/link-features.test.js.map +1 -0
  57. package/dist/tests/driver/usage.test.d.ts +1 -0
  58. package/dist/tests/driver/usage.test.js +88 -0
  59. package/dist/tests/driver/usage.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-audio.test.js +4 -4
  61. package/dist/tests/e2e/gui-audio.test.js.map +1 -1
  62. package/dist/tests/e2e/gui-css-coverage.test.d.ts +9 -0
  63. package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
  64. package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
  66. package/dist/tests/e2e/gui-css-features.test.js +270 -0
  67. package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-effects.test.js +92 -11
  69. package/dist/tests/e2e/gui-effects.test.js.map +1 -1
  70. package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
  71. package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
  72. package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
  73. package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
  74. package/dist/tests/e2e/gui-font-face.test.js +142 -0
  75. package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
  76. package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
  77. package/dist/tests/e2e/gui-fonts.test.js +150 -0
  78. package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
  79. package/dist/tests/e2e/gui-helpers.d.ts +10 -1
  80. package/dist/tests/e2e/gui-helpers.js +6 -0
  81. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  82. package/dist/tests/e2e/gui-layout.test.js +7 -3
  83. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  84. package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
  85. package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
  86. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
  87. package/dist/tests/e2e/gui-media.test.d.ts +5 -4
  88. package/dist/tests/e2e/gui-media.test.js +9 -9
  89. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  90. package/dist/tests/e2e/gui-paint.test.js +5 -2
  91. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  92. package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
  93. package/dist/tests/e2e/gui-shaping.test.js +47 -0
  94. package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
  95. package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
  96. package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
  97. package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
  98. package/dist/tests/e2e/gui-transform.test.js +3 -2
  99. package/dist/tests/e2e/gui-transform.test.js.map +1 -1
  100. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
  101. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
  102. package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
  103. package/dist/tests/extensions/gui-features.test.d.ts +1 -0
  104. package/dist/tests/extensions/gui-features.test.js +175 -0
  105. package/dist/tests/extensions/gui-features.test.js.map +1 -0
  106. package/doc/ai/cli.md +5 -1
  107. package/doc/ai/extensions.md +56 -26
  108. package/doc/ai/troubleshooting.md +10 -7
  109. package/doc/ai/zh-CN/cli.md +5 -1
  110. package/doc/ai/zh-CN/extensions.md +48 -22
  111. package/doc/ai/zh-CN/troubleshooting.md +6 -5
  112. package/doc/gui/animation.md +39 -9
  113. package/doc/gui/api.md +2 -1
  114. package/doc/gui/build.md +137 -19
  115. package/doc/gui/canvas.md +1 -1
  116. package/doc/gui/dom.md +20 -4
  117. package/doc/gui/known-issues.md +63 -26
  118. package/doc/gui/layout.md +31 -8
  119. package/doc/gui/media.md +13 -7
  120. package/doc/gui/rendering.md +114 -39
  121. package/doc/gui/runtime.md +5 -0
  122. package/doc/gui/webgl.md +24 -6
  123. package/doc/gui.md +110 -47
  124. package/doc/implemented/cli.md +2 -0
  125. package/doc/implemented/extensions.md +9 -3
  126. package/doc/npm-package.md +4 -4
  127. package/doc/xbintsc.config.schema.json +10 -0
  128. package/doc/zh-CN/gui/animation.md +33 -10
  129. package/doc/zh-CN/gui/api.md +2 -1
  130. package/doc/zh-CN/gui/build.md +110 -16
  131. package/doc/zh-CN/gui/canvas.md +1 -1
  132. package/doc/zh-CN/gui/dom.md +16 -3
  133. package/doc/zh-CN/gui/known-issues.md +45 -15
  134. package/doc/zh-CN/gui/layout.md +23 -6
  135. package/doc/zh-CN/gui/media.md +11 -6
  136. package/doc/zh-CN/gui/rendering.md +84 -26
  137. package/doc/zh-CN/gui/runtime.md +3 -0
  138. package/doc/zh-CN/gui/webgl.md +3 -3
  139. package/doc/zh-CN/gui.md +92 -41
  140. package/doc/zh-CN/implemented/cli.md +2 -0
  141. package/doc/zh-CN/implemented/extensions.md +9 -3
  142. package/doc/zh-CN/npm-package.md +4 -3
  143. package/package.json +7 -7
  144. package/runtime/ext_gui/animation_values.cpp +425 -0
  145. package/runtime/ext_gui/animation_values.h +64 -0
  146. package/runtime/ext_gui/browser.cpp +22 -15
  147. package/runtime/ext_gui/browser_loop.cpp +13 -1
  148. package/runtime/ext_gui/canvas.h +1 -1
  149. package/runtime/ext_gui/canvas_raster.cpp +1 -1
  150. package/runtime/ext_gui/canvas_text.cpp +1 -1
  151. package/runtime/ext_gui/css.cpp +172 -21
  152. package/runtime/ext_gui/css.h +75 -4
  153. package/runtime/ext_gui/css_match.cpp +4 -0
  154. package/runtime/ext_gui/css_sheet.cpp +218 -2
  155. package/runtime/ext_gui/document.cpp +42 -192
  156. package/runtime/ext_gui/document.h +25 -12
  157. package/runtime/ext_gui/document_animation.cpp +428 -0
  158. package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
  159. package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
  160. package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
  161. package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
  162. package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
  163. package/runtime/ext_gui/dom_api_style.cpp +7 -0
  164. package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
  165. package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
  166. package/runtime/ext_gui/effect_values.cpp +245 -21
  167. package/runtime/ext_gui/gpu_bridge.c +28 -0
  168. package/runtime/ext_gui/gpu_feature.cpp +30 -0
  169. package/runtime/ext_gui/gpu_shader.cpp +22 -0
  170. package/runtime/ext_gui/gpu_shader_api.h +50 -0
  171. package/runtime/ext_gui/gradient_values.cpp +24 -0
  172. package/runtime/ext_gui/grid_values.cpp +23 -0
  173. package/runtime/ext_gui/gui.cpp +6 -37
  174. package/runtime/ext_gui/gui_canvas.cpp +14 -6
  175. package/runtime/ext_gui/gui_cursor.cpp +50 -0
  176. package/runtime/ext_gui/gui_engine.h +1 -18
  177. package/runtime/ext_gui/gui_events.cpp +19 -6
  178. package/runtime/ext_gui/gui_internal.h +3 -0
  179. package/runtime/ext_gui/gui_webgl.cpp +25 -10
  180. package/runtime/ext_gui/image_api.cpp +18 -0
  181. package/runtime/ext_gui/image_api.h +43 -0
  182. package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
  183. package/runtime/ext_gui/image_bridge.c +22 -0
  184. package/runtime/ext_gui/image_feature.cpp +107 -0
  185. package/runtime/ext_gui/inline_svg.cpp +95 -8
  186. package/runtime/ext_gui/inline_svg.h +5 -1
  187. package/runtime/ext_gui/layout.cpp +15 -9
  188. package/runtime/ext_gui/layout.h +8 -2
  189. package/runtime/ext_gui/layout_flex.cpp +134 -29
  190. package/runtime/ext_gui/layout_flow.cpp +58 -7
  191. package/runtime/ext_gui/layout_flow_internal.h +39 -4
  192. package/runtime/ext_gui/layout_grid.cpp +31 -0
  193. package/runtime/ext_gui/layout_inline.cpp +26 -2
  194. package/runtime/ext_gui/layout_vertical.cpp +196 -0
  195. package/runtime/ext_gui/media.cpp +4 -2
  196. package/runtime/ext_gui/media.h +3 -3
  197. package/runtime/ext_gui/media_api.h +7 -6
  198. package/runtime/ext_gui/paint.cpp +67 -9
  199. package/runtime/ext_gui/paint.h +61 -8
  200. package/runtime/ext_gui/paint_backdrop.cpp +211 -0
  201. package/runtime/ext_gui/paint_background.cpp +175 -97
  202. package/runtime/ext_gui/paint_effects.cpp +12 -6
  203. package/runtime/ext_gui/paint_internal.h +27 -0
  204. package/runtime/ext_gui/paint_outline.cpp +44 -0
  205. package/runtime/ext_gui/paint_shadow.cpp +368 -0
  206. package/runtime/ext_gui/renderer.cpp +15 -7
  207. package/runtime/ext_gui/renderer.h +11 -2
  208. package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
  209. package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
  210. package/runtime/ext_gui/renderer_gradient.cpp +51 -17
  211. package/runtime/ext_gui/renderer_image.cpp +19 -7
  212. package/runtime/ext_gui/renderer_internal.h +21 -0
  213. package/runtime/ext_gui/renderer_layer.cpp +37 -6
  214. package/runtime/ext_gui/renderer_text.cpp +2 -1
  215. package/runtime/ext_gui/stb_truetype.h +5077 -0
  216. package/runtime/ext_gui/style.cpp +62 -8
  217. package/runtime/ext_gui/style.h +111 -4
  218. package/runtime/ext_gui/style_cascade.cpp +83 -0
  219. package/runtime/ext_gui/style_internal.h +13 -1
  220. package/runtime/ext_gui/style_length.cpp +92 -0
  221. package/runtime/ext_gui/style_print.cpp +83 -2
  222. package/runtime/ext_gui/style_svg.cpp +40 -0
  223. package/runtime/ext_gui/style_text.cpp +302 -0
  224. package/runtime/ext_gui/style_ui.cpp +150 -0
  225. package/runtime/ext_gui/style_values.cpp +76 -62
  226. package/runtime/ext_gui/style_values_internal.h +23 -0
  227. package/runtime/ext_gui/text.cpp +27 -499
  228. package/runtime/ext_gui/text.h +18 -9
  229. package/runtime/ext_gui/text_api.h +45 -0
  230. package/runtime/ext_gui/text_bridge.c +23 -0
  231. package/runtime/ext_gui/text_cache.cpp +12 -3
  232. package/runtime/ext_gui/text_font_db.cpp +427 -0
  233. package/runtime/ext_gui/text_font_paths.cpp +250 -0
  234. package/runtime/ext_gui/text_font_paths.h +39 -0
  235. package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
  236. package/runtime/ext_gui/text_font_sfnt.h +44 -0
  237. package/runtime/ext_gui/text_fonts.cpp +348 -0
  238. package/runtime/ext_gui/text_fonts.h +78 -0
  239. package/runtime/ext_gui/text_fonts_internal.h +77 -0
  240. package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
  241. package/runtime/ext_gui/text_hb.cpp +438 -0
  242. package/runtime/ext_gui/text_stb.cpp +369 -0
  243. package/runtime/ext_gui/transform.cpp +107 -9
  244. package/runtime/ext_gui/transform.h +5 -0
  245. package/runtime/ext_gui/webgl_api.cpp +19 -0
  246. package/runtime/ext_gui/webgl_api.h +53 -0
  247. package/runtime/ext_gui/webgl_bridge.c +25 -0
  248. package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
  249. package/runtime/ext_gui/webgl_feature.cpp +82 -0
  250. package/runtime/ext_gui/webgl_feature.h +28 -0
  251. package/runtime/ext_gui/webgl_msl.cpp +41 -0
  252. package/runtime/ext_gui/webgl_msl.h +6 -0
  253. package/runtime/ext_gui/webgl_shader.cpp +40 -32
  254. package/runtime/ext_gui/window.cpp +7 -7
  255. package/runtime/ext_media/decode_audio.cpp +1 -1
  256. package/runtime/ext_media/media.cpp +1 -1
  257. package/runtime/ext_media/media_bridge.c +7 -7
  258. package/scripts/build-gui.ts +97 -98
  259. package/scripts/check-file-length.ts +1 -0
  260. package/scripts/gui-build-options.ts +122 -0
  261. package/src/cli/extensions.ts +72 -0
  262. package/src/cli/help.ts +3 -1
  263. package/src/cli/hints.ts +9 -3
  264. package/src/cli/main.ts +21 -27
  265. package/src/diagnostics/diagnostic.ts +2 -0
  266. package/src/driver/build-key.ts +3 -0
  267. package/src/driver/compiler.ts +63 -8
  268. package/src/driver/config.ts +6 -0
  269. package/src/driver/link-features.ts +163 -0
  270. package/src/driver/options.ts +10 -0
  271. package/src/driver/runtime-objects.ts +26 -1
  272. package/src/driver/usage.ts +376 -0
  273. package/src/driver/version.ts +1 -1
  274. package/src/extensions/catalog.ts +1 -2
  275. package/src/extensions/gui/features.ts +166 -0
  276. package/src/extensions/gui/index.ts +10 -4
  277. package/src/extensions/link-feature.ts +72 -0
  278. package/src/extensions/registry.ts +45 -4
  279. package/src/index.ts +3 -2
  280. package/dist/src/extensions/media/index.d.ts +0 -28
  281. package/dist/src/extensions/media/index.js +0 -59
  282. package/dist/src/extensions/media/index.js.map +0 -1
  283. package/dist/tests/extensions/media.test.js +0 -63
  284. package/dist/tests/extensions/media.test.js.map +0 -1
  285. package/src/extensions/media/index.ts +0 -65
  286. /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
package/src/cli/main.ts CHANGED
@@ -31,9 +31,7 @@ import {
31
31
  toolchainPointer,
32
32
  UNEXPECTED_FAILURE_HINT,
33
33
  } from "./hints.js";
34
- import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
35
- import { bundledExtensions } from "../extensions/catalog.js";
36
- import { nativeExtensionFromManifest } from "../extensions/native.js";
34
+ import { buildRegistry, normalizeExtensions } from "./extensions.js";
37
35
  import { resolveResourceCompiler } from "../driver/win-icon.js";
38
36
  import {
39
37
  CONFIG_FILE_NAME,
@@ -80,6 +78,8 @@ const VALUE_FLAGS = new Set([
80
78
  "optimize",
81
79
  "ext",
82
80
  "ext-native",
81
+ "feature",
82
+ "no-feature",
83
83
  "config",
84
84
  "icon",
85
85
  "app-id",
@@ -138,27 +138,6 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
138
138
  return { command: positionals.shift(), positionals, flags, passthrough };
139
139
  }
140
140
 
141
- function buildRegistry(extNames: readonly string[], nativeManifests: readonly string[]): ExtensionRegistry {
142
- const registry = createDefaultRegistry();
143
- const bundled = bundledExtensions();
144
- for (const name of extNames) {
145
- const extension = bundled.find((candidate) => candidate.name === name);
146
- if (!extension) throw new Error(`Unknown extension '${name}'`);
147
- registry.register(extension);
148
- }
149
- // Each manifest describes a pre-built C++ or Rust library.
150
- for (const manifest of nativeManifests) {
151
- registry.register(nativeExtensionFromManifest(manifest));
152
- }
153
- // Hint the extensions that ship with xbintsc but were not enabled, so a
154
- // missing `import` of a known module points at the flag that enables it
155
- // (`pass --ext node`) instead of failing later with a confusing error.
156
- for (const extension of bundled) {
157
- if (!registry.has(extension.name)) registry.hintExtension(extension);
158
- }
159
- return registry;
160
- }
161
-
162
141
  function printDiagnostics(diagnostics: readonly Diagnostic[], fileName: string, io: CliIo): void {
163
142
  /* A diagnostic can come from any module in a bundled program, so load (and
164
143
  * cache) the source of the file it names instead of always using the entry:
@@ -404,7 +383,8 @@ function dispatch(argv: readonly string[], io: CliIo): number {
404
383
  }
405
384
  const extNames = listFlag(args.flags, "ext") ?? resolved.config.extensions ?? [];
406
385
  const nativeManifests = listFlag(args.flags, "ext-native") ?? resolved.config.extNative ?? [];
407
- const registry = buildRegistry(extNames, nativeManifests);
386
+ const { extensions: registryExtensions } = normalizeExtensions(extNames, io);
387
+ const registry = buildRegistry(registryExtensions, nativeManifests);
408
388
  /* Front-end only: parse, bundle, check and lower far enough to report
409
389
  * semantic / unsupported-feature diagnostics, but never invoke clang or
410
390
  * write output. The checker is on unless `--no-check` is passed. */
@@ -458,7 +438,8 @@ function dispatch(argv: readonly string[], io: CliIo): number {
458
438
  }
459
439
  const extNames = listFlag(args.flags, "ext") ?? resolved.config.extensions ?? [];
460
440
  const nativeManifests = listFlag(args.flags, "ext-native") ?? resolved.config.extNative ?? [];
461
- const registry = buildRegistry(extNames, nativeManifests);
441
+ const { extensions: registryExtensions } = normalizeExtensions(extNames, io);
442
+ const registry = buildRegistry(registryExtensions, nativeManifests);
462
443
  const { ir, diagnostics } = compileEntry(entry, registry, {
463
444
  debug: Boolean(args.flags.get("debug")),
464
445
  check: !args.flags.has("no-check"),
@@ -490,7 +471,13 @@ function dispatch(argv: readonly string[], io: CliIo): number {
490
471
  const optimize = (args.flags.get("optimize") as string | undefined) ?? config.optimize ?? "2";
491
472
  const extNames = listFlag(args.flags, "ext") ?? config.extensions ?? [];
492
473
  const nativeManifests = listFlag(args.flags, "ext-native") ?? config.extNative ?? [];
493
- const registry = buildRegistry(extNames, nativeManifests);
474
+ const normalized = normalizeExtensions(extNames, io);
475
+ const registry = buildRegistry(normalized.extensions, nativeManifests);
476
+ const features = [
477
+ ...normalized.features,
478
+ ...(listFlag(args.flags, "feature") ?? config.features ?? []),
479
+ ];
480
+ const noFeatures = listFlag(args.flags, "no-feature") ?? config.noFeatures ?? [];
494
481
  const pgoGenerate = args.flags.get("pgo-generate");
495
482
  const pgoUse = args.flags.get("pgo-use");
496
483
  if (pgoGenerate !== undefined && pgoUse !== undefined) {
@@ -524,6 +511,8 @@ function dispatch(argv: readonly string[], io: CliIo): number {
524
511
  lto: Boolean(args.flags.get("lto")) || config.lto === true,
525
512
  ...(pgo ? { pgo } : {}),
526
513
  ...(pgoDir ? { pgoDir } : {}),
514
+ ...(features.length > 0 ? { features } : {}),
515
+ ...(noFeatures.length > 0 ? { noFeatures } : {}),
527
516
  });
528
517
 
529
518
  if (result.diagnostics.some((d) => d.category === "error")) {
@@ -535,6 +524,11 @@ function dispatch(argv: readonly string[], io: CliIo): number {
535
524
  return 1;
536
525
  }
537
526
 
527
+ /* A detected-but-unavailable feature is a warning, not a failure: the
528
+ * program still links and the capability degrades at run time. */
529
+ const warnings = result.diagnostics.filter((d) => d.category === "warning");
530
+ if (warnings.length > 0) printDiagnostics(warnings, entry, io);
531
+
538
532
  if (args.flags.has("time") && result.timings) printTimings(result.timings, io);
539
533
 
540
534
  if (command === "build") {
@@ -44,6 +44,8 @@ export enum DiagnosticCode {
44
44
  ToolchainError = 6003,
45
45
  IncrementalCacheError = 6004,
46
46
  CircularDependency = 6005,
47
+ /** An optional link feature was requested but its archive is unavailable. */
48
+ LinkFeatureUnavailable = 6006,
47
49
  }
48
50
 
49
51
  export interface Diagnostic {
@@ -31,6 +31,8 @@ export interface CacheKeyInput {
31
31
  readonly check?: boolean;
32
32
  readonly pgo?: PgoMode;
33
33
  readonly pgoDir: string;
34
+ /** Active optional link features; they change which objects/archives link. */
35
+ readonly features?: readonly string[];
34
36
  }
35
37
 
36
38
  /** Hash every input that can change the produced binary. */
@@ -54,5 +56,6 @@ export function buildCacheKey(input: CacheKeyInput): string {
54
56
  String(input.check !== false),
55
57
  pgo ?? "",
56
58
  pgo ? input.pgoDir : "",
59
+ [...(input.features ?? [])].sort().join(","),
57
60
  ]);
58
61
  }
@@ -28,6 +28,8 @@ import { parseEntry } from "./frontend.js";
28
28
  import { ensureRuntimeObjects } from "./runtime-objects.js";
29
29
  import { ensureAppArtifacts } from "./app-artifacts.js";
30
30
  import { buildCacheKey } from "./build-key.js";
31
+ import { collectUsage } from "./usage.js";
32
+ import { resolveLinkFeatures, type ResolvedLinkFeature } from "./link-features.js";
31
33
  import type { BuildOptions, BuildResult, EmitKind, PhaseTiming } from "./options.js";
32
34
 
33
35
  export { COMPILER_VERSION } from "./version.js";
@@ -102,8 +104,35 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
102
104
  return { outputPath, cached: false, diagnostics: diagnostics.diagnostics, timings };
103
105
  }
104
106
 
107
+ /* Optional link features are selected from the bundled program before the
108
+ * cache key: they change which registration objects and archives link. Only
109
+ * a linked `exe` has features; `emit obj`/`ir` produce toolchain input that
110
+ * the caller may link itself. */
105
111
  const app: AppConfig = options.app ?? {};
106
112
  const iconPath = options.icon ?? app.icon;
113
+ /* An app icon is decoded through the image pipeline, so force `images` even
114
+ * when the bundled HTML has no `<img>` — but only when the enabled extensions
115
+ * actually declare that feature (a non-GUI program may still embed an icon). */
116
+ const hasImagesFeature = registry.linkFeatures().some((entry) => entry.feature.name === "images");
117
+ const featureRequest = {
118
+ features: [
119
+ ...(options.features ?? []),
120
+ ...(iconPath !== undefined && hasImagesFeature ? ["images"] : []),
121
+ ],
122
+ noFeatures: options.noFeatures,
123
+ };
124
+ const linkFeatures: readonly ResolvedLinkFeature[] =
125
+ emit === "exe"
126
+ ? resolveLinkFeatures(registry, collectUsage(parsed.sourceFile), featureRequest, diagnostics)
127
+ : [];
128
+ if (diagnostics.hasErrors) {
129
+ return { outputPath, cached: false, diagnostics: diagnostics.diagnostics, timings };
130
+ }
131
+ if (options.verbose) {
132
+ const names = linkFeatures.map((feature) => feature.name);
133
+ process.stderr.write(`xbintsc: link features: ${names.length > 0 ? names.join(", ") : "none"}\n`);
134
+ }
135
+
107
136
  let icon: IconInfo | undefined;
108
137
  if (iconPath) {
109
138
  try {
@@ -131,6 +160,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
131
160
  check: options.check !== false,
132
161
  pgo,
133
162
  pgoDir,
163
+ features: linkFeatures.map((feature) => feature.name),
134
164
  });
135
165
 
136
166
  // A `.app` bundle is deterministic from the output path and name, so it can be
@@ -144,7 +174,14 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
144
174
  /* PGO profile data can change without the cache key changing, so always
145
175
  * rebuild a `--pgo-use` binary; it is an explicit optimization step. */
146
176
  if (!options.force && pgo !== "use" && !diagnostics.hasErrors && cache.isFresh(cacheKey, outputs)) {
147
- return { outputPath, cached: true, diagnostics: [], timings, ...(bundle ? { bundlePath: bundle } : {}) };
177
+ return {
178
+ outputPath,
179
+ cached: true,
180
+ diagnostics: [],
181
+ timings,
182
+ features: linkFeatures.map((feature) => feature.name),
183
+ ...(bundle ? { bundlePath: bundle } : {}),
184
+ };
148
185
  }
149
186
 
150
187
  const codegenStarted = Date.now();
@@ -220,7 +257,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
220
257
  }
221
258
 
222
259
  const runtimeStarted = Date.now();
223
- const { runtimeObjects, extensionObjects, nativeObjects } = ensureRuntimeObjects({
260
+ const { runtimeObjects, extensionObjects, featureObjects, nativeObjects } = ensureRuntimeObjects({
224
261
  runner,
225
262
  clang,
226
263
  runtimeDir,
@@ -231,6 +268,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
231
268
  lto,
232
269
  pgo,
233
270
  pgoDir,
271
+ features: linkFeatures,
234
272
  });
235
273
  timings.push({ phase: "runtime", ms: Date.now() - runtimeStarted });
236
274
 
@@ -254,17 +292,25 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
254
292
  clang,
255
293
  objectPaths: [
256
294
  objectPath,
257
- ...runtimeObjects,
258
- ...extensionObjects,
259
- ...nativeObjects,
260
- ...(artifacts.entryObject !== undefined ? [artifacts.entryObject] : []),
261
- ...artifacts.iconObjects,
295
+ /* Feature archives come after their bridges (which reference the
296
+ * implementation) and before the extensions' own archives (`gui.a`
297
+ * provides symbols the bridges call back into). `Set` keeps that order
298
+ * and drops an archive two features or an extension share. */
299
+ ...new Set([
300
+ ...runtimeObjects,
301
+ ...extensionObjects,
302
+ ...featureObjects,
303
+ ...nativeObjects,
304
+ ...(artifacts.entryObject !== undefined ? [artifacts.entryObject] : []),
305
+ ...artifacts.iconObjects,
306
+ ]),
262
307
  ],
263
308
  outputPath,
264
309
  linkerFlags: [
265
310
  ...toolchain.linkerArgs,
266
311
  ...(process.platform === "win32" ? ["-lws2_32"] : ["-lm", "-pthread"]),
267
312
  ...registry.linkerFlags(),
313
+ ...linkFeatures.flatMap((feature) => feature.linkerFlags),
268
314
  /* A GUI program is a Windows-subsystem executable: no console window is
269
315
  * created when it is launched. Console output (stdout/stderr) is lost in
270
316
  * that subsystem, so a program that wants to report things opts back in
@@ -292,7 +338,16 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
292
338
 
293
339
  cache.record(cacheKey, outputs);
294
340
  cache.save();
295
- return { outputPath, irPath, cached: false, diagnostics: [], ir, timings, ...(bundlePath ? { bundlePath } : {}) };
341
+ return {
342
+ outputPath,
343
+ irPath,
344
+ cached: false,
345
+ diagnostics: [],
346
+ ir,
347
+ timings,
348
+ features: linkFeatures.map((feature) => feature.name),
349
+ ...(bundlePath ? { bundlePath } : {}),
350
+ };
296
351
  }
297
352
 
298
353
  /** Default `CFBundleIdentifier` for a bundle when none is configured. */
@@ -44,6 +44,10 @@ export interface ProjectConfig {
44
44
  readonly pgoDir?: string;
45
45
  readonly extensions?: readonly string[];
46
46
  readonly extNative?: readonly string[];
47
+ /** Optional link features to force on (`media`, `gpu`, `images`, `shaping`, `webgl`, `all`). */
48
+ readonly features?: readonly string[];
49
+ /** Optional link features to keep out even when auto-detection finds a use. */
50
+ readonly noFeatures?: readonly string[];
47
51
  readonly force?: boolean;
48
52
  readonly app?: AppConfig;
49
53
  }
@@ -135,6 +139,8 @@ export function parseProjectConfig(text: string, where: string = CONFIG_FILE_NAM
135
139
  pgoDir: readString(root, "pgoDir", where),
136
140
  extensions: readStringArray(root, "extensions", where),
137
141
  extNative: readStringArray(root, "extNative", where),
142
+ features: readStringArray(root, "features", where),
143
+ noFeatures: readStringArray(root, "noFeatures", where),
138
144
  force: readBoolean(root, "force", where),
139
145
  app,
140
146
  };
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Resolving optional link features for one build.
3
+ *
4
+ * The registry knows which features the *enabled* extensions can provide; this
5
+ * module decides which of them a program may use (from {@link ProgramUsage}),
6
+ * applies the `--feature` / `--no-feature` overrides, and turns an unavailable
7
+ * archive into either a warning (auto-detected) or an error (explicitly forced).
8
+ *
9
+ * Keeping the decision here — not in codegen — means the produced LLVM IR is
10
+ * independent of the feature set: the only difference is which registration
11
+ * objects and archives reach the linker.
12
+ */
13
+
14
+ import { DiagnosticCode, type DiagnosticBag } from "../diagnostics/diagnostic.js";
15
+ import type { ExtensionRegistry } from "../extensions/registry.js";
16
+ import type { LinkFeature, ProgramUsage } from "../extensions/link-feature.js";
17
+
18
+ /** The user's explicit feature overrides (`--feature` / `--no-feature`). */
19
+ export interface LinkFeatureRequest {
20
+ readonly features?: readonly string[];
21
+ readonly noFeatures?: readonly string[];
22
+ }
23
+
24
+ /** One active feature, with everything the driver needs to link it. */
25
+ export interface ResolvedLinkFeature {
26
+ readonly name: string;
27
+ readonly description: string;
28
+ readonly runtimeSources: readonly string[];
29
+ readonly nativeObjects: readonly string[];
30
+ readonly linkerFlags: readonly string[];
31
+ }
32
+
33
+ /** `--feature all` / `--no-feature all`; specific names override it. */
34
+ const ALL = "all";
35
+
36
+ /** Human-readable list of the features the enabled extensions provide. */
37
+ function availableList(names: readonly string[]): string {
38
+ if (names.length === 0) return "none (enable an extension that provides one, e.g. --ext gui)";
39
+ return names.join(", ");
40
+ }
41
+
42
+ /**
43
+ * Pick the active features for `usage`.
44
+ *
45
+ * Precedence: a specific `--feature x` beats `--no-feature all`, and a specific
46
+ * `--no-feature x` beats `--feature all`; the same name on both sides is an
47
+ * error. Diagnosed errors are left in `diagnostics`; the caller must stop when
48
+ * it has errors.
49
+ */
50
+ export function resolveLinkFeatures(
51
+ registry: ExtensionRegistry,
52
+ usage: ProgramUsage,
53
+ request: LinkFeatureRequest,
54
+ diagnostics: DiagnosticBag,
55
+ ): readonly ResolvedLinkFeature[] {
56
+ const declarations = registry.linkFeatures();
57
+ const byName = new Map<string, LinkFeature>();
58
+ for (const declaration of declarations) byName.set(declaration.feature.name, declaration.feature);
59
+ const available = [...byName.keys()].sort();
60
+ const requested = new Set(request.features ?? []);
61
+ const disabled = new Set(request.noFeatures ?? []);
62
+
63
+ for (const name of [...requested, ...disabled]) {
64
+ if (name === ALL || byName.has(name)) continue;
65
+ diagnostics.error(
66
+ DiagnosticCode.LinkFeatureUnavailable,
67
+ `unknown link feature '${name}'; available: ${availableList(available)}`,
68
+ );
69
+ }
70
+ if (requested.has(ALL) && disabled.has(ALL)) {
71
+ diagnostics.error(
72
+ DiagnosticCode.LinkFeatureUnavailable,
73
+ "link features cannot be both forced and disabled with 'all' (--feature all --no-feature all)",
74
+ );
75
+ }
76
+ if (diagnostics.hasErrors) return [];
77
+
78
+ const forceAll = requested.has(ALL);
79
+ const disableAll = disabled.has(ALL);
80
+
81
+ /* Start from the explicitly forced set, then pull in `requires`. A specific
82
+ * `--no-feature` wins over `--feature all`, so it is excluded from the base
83
+ * set; a dependency it blocks is an error rather than a silent omission. */
84
+ const forced = new Set<string>();
85
+ for (const { feature } of declarations) {
86
+ const specificallyForced = requested.has(feature.name);
87
+ const specificallyBlocked = disabled.has(feature.name);
88
+ if (specificallyForced && specificallyBlocked) {
89
+ diagnostics.error(
90
+ DiagnosticCode.LinkFeatureUnavailable,
91
+ `link feature '${feature.name}' is both forced (--feature) and disabled (--no-feature)`,
92
+ );
93
+ continue;
94
+ }
95
+ if (specificallyForced || (forceAll && !specificallyBlocked)) forced.add(feature.name);
96
+ }
97
+ let changed = true;
98
+ while (changed) {
99
+ changed = false;
100
+ for (const { feature } of declarations) {
101
+ if (!forced.has(feature.name)) continue;
102
+ for (const dependency of feature.requires ?? []) {
103
+ if (!byName.has(dependency)) {
104
+ diagnostics.error(
105
+ DiagnosticCode.LinkFeatureUnavailable,
106
+ `link feature '${feature.name}' requires unknown feature '${dependency}'`,
107
+ );
108
+ continue;
109
+ }
110
+ if (disabled.has(dependency)) {
111
+ diagnostics.error(
112
+ DiagnosticCode.LinkFeatureUnavailable,
113
+ `link feature '${feature.name}' requires '${dependency}', which --no-feature disables`,
114
+ );
115
+ continue;
116
+ }
117
+ if (!forced.has(dependency)) {
118
+ forced.add(dependency);
119
+ changed = true;
120
+ }
121
+ }
122
+ }
123
+ }
124
+ if (diagnostics.hasErrors) return [];
125
+
126
+ const resolved: ResolvedLinkFeature[] = [];
127
+
128
+ for (const { feature } of declarations) {
129
+ const specificallyBlocked = disabled.has(feature.name);
130
+ const forcedHere = forced.has(feature.name);
131
+ const blocked = specificallyBlocked || (disableAll && !requested.has(feature.name) && !forcedHere);
132
+ if (!forcedHere && blocked) continue;
133
+ if (!forcedHere && !feature.used(usage)) continue;
134
+
135
+ let runtimeSources: readonly string[];
136
+ let nativeObjects: readonly string[];
137
+ try {
138
+ runtimeSources = feature.runtimeSources?.() ?? [];
139
+ nativeObjects = feature.nativeObjects?.() ?? [];
140
+ } catch (error) {
141
+ const message = error instanceof Error ? error.message : String(error);
142
+ if (forcedHere) {
143
+ diagnostics.error(DiagnosticCode.LinkFeatureUnavailable, message);
144
+ } else {
145
+ diagnostics.warning(
146
+ DiagnosticCode.LinkFeatureUnavailable,
147
+ `link feature '${feature.name}' was detected but is unavailable: ${message}`,
148
+ );
149
+ }
150
+ continue;
151
+ }
152
+
153
+ resolved.push({
154
+ name: feature.name,
155
+ description: feature.description,
156
+ runtimeSources: [...runtimeSources],
157
+ nativeObjects: [...nativeObjects],
158
+ linkerFlags: [...(feature.linkerFlags?.() ?? [])],
159
+ });
160
+ }
161
+
162
+ return resolved;
163
+ }
@@ -50,6 +50,14 @@ export interface BuildOptions {
50
50
  readonly pgo?: PgoMode;
51
51
  /** Profile directory (default: `<outDir>/pgo`). */
52
52
  readonly pgoDir?: string;
53
+ /**
54
+ * Optional link features to force on (`media`, `gpu`, `images`, `shaping`, `webgl`, or `all`). Features are
55
+ * otherwise selected from a static scan of the bundled program; forcing one
56
+ * is the escape hatch for HTML/CSS built from runtime data.
57
+ */
58
+ readonly features?: readonly string[];
59
+ /** Optional link features to keep out even when the scan detects a use. */
60
+ readonly noFeatures?: readonly string[];
53
61
  }
54
62
 
55
63
  /** One phase of a build, in milliseconds (reported by `--time`). */
@@ -68,6 +76,8 @@ export interface BuildResult {
68
76
  readonly bundlePath?: string;
69
77
  /** Per-phase timings, collected for `xbintsc build --time`. */
70
78
  readonly timings?: readonly PhaseTiming[];
79
+ /** Active optional link features (only for a linked `exe` build). */
80
+ readonly features?: readonly string[];
71
81
  }
72
82
 
73
83
  export interface CompileStringResult {
@@ -9,6 +9,7 @@
9
9
  import { existsSync, readFileSync, readdirSync } from "node:fs";
10
10
  import { basename, join, resolve } from "node:path";
11
11
  import type { ExtensionRegistry } from "../extensions/registry.js";
12
+ import type { ResolvedLinkFeature } from "./link-features.js";
12
13
  import { hashParts } from "./cache.js";
13
14
  import { findRuntimeLibrary } from "./runtime-lib.js";
14
15
  import { compileC, optimizationFlags, RUNTIME_CFLAGS, type PgoMode, type Runner } from "./toolchain.js";
@@ -46,6 +47,8 @@ export const RUNTIME_SOURCES = [
46
47
  export interface RuntimeObjects {
47
48
  readonly runtimeObjects: readonly string[];
48
49
  readonly extensionObjects: readonly string[];
50
+ /** Objects/archives contributed by active optional link features. */
51
+ readonly featureObjects: readonly string[];
49
52
  /** Pre-built C++/Rust objects and archives contributed by native extensions. */
50
53
  readonly nativeObjects: readonly string[];
51
54
  }
@@ -61,6 +64,8 @@ export interface RuntimeObjectsOptions {
61
64
  readonly lto: boolean;
62
65
  readonly pgo: PgoMode | undefined;
63
66
  readonly pgoDir: string;
67
+ /** Optional link features selected for this program. */
68
+ readonly features?: readonly ResolvedLinkFeature[];
64
69
  }
65
70
 
66
71
  /**
@@ -131,5 +136,25 @@ export function ensureRuntimeObjects(options: RuntimeObjectsOptions): RuntimeObj
131
136
  }
132
137
  }
133
138
  // A native archive may be shared by several manifests; link each once.
134
- return { runtimeObjects, extensionObjects, nativeObjects: [...new Set(registry.nativeObjects())] };
139
+ const featureObjects: string[] = [];
140
+ for (const feature of options.features ?? []) {
141
+ /* Feature bridges are always compiled: they are not part of any prebuilt
142
+ * archive, and their references are what make the linker extract the
143
+ * feature's implementation. */
144
+ for (const sourcePath of feature.runtimeSources) {
145
+ const dedupeKey = resolve(sourcePath);
146
+ if (seen.has(dedupeKey)) continue;
147
+ seen.add(dedupeKey);
148
+ extensionObjects.push(
149
+ compileOne(sourcePath, `feature_${feature.name}_${basename(sourcePath, ".c")}`),
150
+ );
151
+ }
152
+ featureObjects.push(...feature.nativeObjects);
153
+ }
154
+ return {
155
+ runtimeObjects,
156
+ extensionObjects,
157
+ featureObjects: [...new Set(featureObjects)],
158
+ nativeObjects: [...new Set(registry.nativeObjects())],
159
+ };
135
160
  }