emsdk-env 0.7.0 → 0.9.0

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.
@@ -1,17 +1,17 @@
1
1
  /*!
2
2
  * name: emsdk-env
3
- * version: 0.7.0
3
+ * version: 0.9.0
4
4
  * description: Emscripten environment builder
5
5
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
6
6
  * license: MIT
7
7
  * repository.url: https://github.com/kekyo/emsdk-env
8
- * git.commit.hash: ddec7e7cc6c59a23b5c87cbd7f99f44e4da3e4b7
8
+ * git.commit.hash: 73d7e217903f020ef22daa9289362dcd19bddac1
9
9
  */
10
10
 
11
- import { mkdir, access, constants, mkdtemp, rename, rm, readFile } from "fs/promises";
11
+ import { mkdir, access, constants, mkdtemp, rename, rm, writeFile, readFile } from "fs/promises";
12
12
  import { homedir, tmpdir } from "os";
13
13
  import { glob } from "glob";
14
- import { join, resolve, isAbsolute, dirname, relative } from "path";
14
+ import { join, resolve, relative, isAbsolute, dirname, parse } from "path";
15
15
  import { spawn } from "child_process";
16
16
  const __NOOP_HANDLER = () => {
17
17
  };
@@ -552,6 +552,11 @@ const DEFAULT_IMPORT_LIB_DIR = "lib";
552
552
  const DEFAULT_WASM_BUILD_DIR = join(tmpdir(), "emsdk-env");
553
553
  const DEFAULT_EMSDK_TARGET_VERSION = "latest";
554
554
  const DEFAULT_WASM_OPT_ARGS = ["-Oz"];
555
+ const DEFAULT_GENERATED_LOADER_OUT_FILE = join(
556
+ "src",
557
+ "generated",
558
+ "wasm-loader.ts"
559
+ );
555
560
  let buildSequence = 0;
556
561
  const padNumber = (value, length = 2) => String(value).padStart(length, "0");
557
562
  const formatTimestamp = (date) => {
@@ -578,9 +583,53 @@ const normalizePrepareOptions = (options) => {
578
583
  ...rest
579
584
  };
580
585
  };
586
+ const parseStringKeyValueInput = (values) => {
587
+ const parsed = {};
588
+ for (const entry of values) {
589
+ const index = entry.indexOf("=");
590
+ if (index === -1) {
591
+ parsed[entry] = void 0;
592
+ continue;
593
+ }
594
+ const key = entry.slice(0, index);
595
+ const value = entry.slice(index + 1);
596
+ parsed[key] = value;
597
+ }
598
+ return parsed;
599
+ };
600
+ const isDefineMap = (input) => input instanceof Map;
601
+ const normalizeDefineInput = (input) => {
602
+ if (!input) {
603
+ return {};
604
+ }
605
+ if (Array.isArray(input)) {
606
+ return parseStringKeyValueInput(input);
607
+ }
608
+ if (isDefineMap(input)) {
609
+ return Object.fromEntries(input);
610
+ }
611
+ return { ...input };
612
+ };
613
+ const isLinkDirectiveMap = (input) => input instanceof Map;
614
+ const normalizeLinkDirectiveInput = (input) => {
615
+ if (!input) {
616
+ return {};
617
+ }
618
+ if (Array.isArray(input)) {
619
+ return parseStringKeyValueInput(input);
620
+ }
621
+ if (isLinkDirectiveMap(input)) {
622
+ return Object.fromEntries(input);
623
+ }
624
+ return { ...input };
625
+ };
581
626
  const mergeDefines = (common, target) => ({
582
- ...common != null ? common : {},
583
- ...target != null ? target : {}
627
+ ...normalizeDefineInput(common),
628
+ ...normalizeDefineInput(target)
629
+ });
630
+ const mergeLinkDirectives = (common, target) => ({
631
+ ...normalizeLinkDirectiveInput(common),
632
+ ...normalizeLinkDirectiveInput(target)
584
633
  });
585
634
  const resolveWasmOptEnabled = (common, target) => {
586
635
  var _a, _b;
@@ -588,10 +637,62 @@ const resolveWasmOptEnabled = (common, target) => {
588
637
  };
589
638
  const resolveWasmOptArgs = (common, target, env) => {
590
639
  var _a, _b;
591
- const commonArgs = (_a = common == null ? void 0 : common.args) != null ? _a : DEFAULT_WASM_OPT_ARGS;
592
- const targetArgs = (_b = target == null ? void 0 : target.args) != null ? _b : [];
640
+ const commonArgs = (_a = common == null ? void 0 : common.options) != null ? _a : DEFAULT_WASM_OPT_ARGS;
641
+ const targetArgs = (_b = target == null ? void 0 : target.options) != null ? _b : [];
593
642
  const mergedArgs = [...commonArgs, ...targetArgs];
594
- return expandArray(mergedArgs, env, "wasmOpt.args");
643
+ return expandArray(mergedArgs, env, "wasmOpt.options");
644
+ };
645
+ const stripOuterQuotes = (value) => {
646
+ const trimmed = value.trim();
647
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
648
+ return trimmed.slice(1, -1);
649
+ }
650
+ return trimmed;
651
+ };
652
+ const extractWasmBinaryFile = (value) => {
653
+ if (value.startsWith("WASM_BINARY_FILE=")) {
654
+ return value.slice("WASM_BINARY_FILE=".length);
655
+ }
656
+ const match = value.match(/^(?:-s|--settings)(?:=)?WASM_BINARY_FILE=(.+)$/);
657
+ if (match) {
658
+ return match[1];
659
+ }
660
+ return void 0;
661
+ };
662
+ const resolveWasmBinaryFileFromLinkOptions = (linkOptions) => {
663
+ for (let index = 0; index < linkOptions.length; index += 1) {
664
+ const option = linkOptions[index];
665
+ if (!option) {
666
+ continue;
667
+ }
668
+ if (option === "-s" || option === "--settings") {
669
+ const next = linkOptions[index + 1];
670
+ if (!next) {
671
+ continue;
672
+ }
673
+ const extracted2 = extractWasmBinaryFile(next);
674
+ if (extracted2) {
675
+ return stripOuterQuotes(extracted2);
676
+ }
677
+ }
678
+ const extracted = extractWasmBinaryFile(option);
679
+ if (extracted) {
680
+ return stripOuterQuotes(extracted);
681
+ }
682
+ }
683
+ return void 0;
684
+ };
685
+ const resolveWasmOptInputFile = (resolvedOutFile, resolvedLinkOptions) => {
686
+ const wasmBinaryFile = resolveWasmBinaryFileFromLinkOptions(resolvedLinkOptions);
687
+ if (wasmBinaryFile) {
688
+ return isAbsolute(wasmBinaryFile) ? wasmBinaryFile : resolve(dirname(resolvedOutFile), wasmBinaryFile);
689
+ }
690
+ const parsed = parse(resolvedOutFile);
691
+ if (parsed.ext.toLowerCase() === ".wasm") {
692
+ return resolvedOutFile;
693
+ }
694
+ const baseName = parsed.name.toLowerCase().endsWith(".wasm") ? parsed.name : `${parsed.name}.wasm`;
695
+ return join(parsed.dir, baseName);
595
696
  };
596
697
  const resolvePath = (rootDir, value) => isAbsolute(value) ? value : resolve(rootDir, value);
597
698
  const expandPlaceholders = (value, env, label) => value.replace(/\{([A-Z0-9_]+)\}/g, (_match, key) => {
@@ -613,6 +714,28 @@ const resolveDefines = (defines, env) => {
613
714
  }
614
715
  return resolved;
615
716
  };
717
+ const resolveLinkDirectiveValue = (value, env, label) => {
718
+ if (typeof value === "string") {
719
+ return expandPlaceholders(value, env, label);
720
+ }
721
+ if (Array.isArray(value)) {
722
+ return value.map(
723
+ (entry, index) => expandPlaceholders(entry, env, `${label}[${index}]`)
724
+ );
725
+ }
726
+ return value;
727
+ };
728
+ const resolveLinkDirectives = (directives, env) => {
729
+ const resolved = {};
730
+ for (const [key, value] of Object.entries(directives)) {
731
+ resolved[key] = resolveLinkDirectiveValue(
732
+ value,
733
+ env,
734
+ `linkDirectives.${key}`
735
+ );
736
+ }
737
+ return resolved;
738
+ };
616
739
  const resolveIncludeDirs = (includeDirs, env, rootDir) => {
617
740
  const expanded = expandArray(includeDirs, env, "includeDirs");
618
741
  return expanded.map((value) => resolvePath(rootDir, value));
@@ -631,7 +754,18 @@ const resolveSourcesFromPatterns = async (patterns, env, srcDir, label) => {
631
754
  sources.sort();
632
755
  return sources;
633
756
  };
634
- const buildDefineFlags = (defines) => Object.entries(defines).map(([key, value]) => `-D${key}=${String(value)}`);
757
+ const buildDefineFlags = (defines) => Object.entries(defines).flatMap(
758
+ ([key, value]) => value === null || value === void 0 ? [`-D${key}`] : [`-D${key}=${String(value)}`]
759
+ );
760
+ const serializeLinkDirectiveValue = (value) => Array.isArray(value) ? JSON.stringify(value) : String(value);
761
+ const buildLinkDirectiveFlags = (directives) => {
762
+ if (Object.keys(directives).length === 0) {
763
+ return [];
764
+ }
765
+ return Object.entries(directives).flatMap(
766
+ ([key, value]) => value === null || value === void 0 ? ["-s", key] : ["-s", `${key}=${serializeLinkDirectiveValue(value)}`]
767
+ );
768
+ };
635
769
  const buildExportFlags = (exports$1) => {
636
770
  if (exports$1.length === 0) {
637
771
  return [];
@@ -684,6 +818,248 @@ const dedupeValues = (values) => {
684
818
  }
685
819
  return deduped;
686
820
  };
821
+ const isSubPath = (parentDir, targetPath) => {
822
+ const rel = relative(parentDir, targetPath);
823
+ if (rel === "") {
824
+ return true;
825
+ }
826
+ return !rel.startsWith("..") && !isAbsolute(rel);
827
+ };
828
+ const readTextIfExists = async (filePath) => {
829
+ try {
830
+ return await readFile(filePath, "utf8");
831
+ } catch (error) {
832
+ const nodeError = error;
833
+ if (nodeError.code === "ENOENT") {
834
+ return void 0;
835
+ }
836
+ throw error;
837
+ }
838
+ };
839
+ const writeTextIfChanged = async (filePath, content) => {
840
+ const existing = await readTextIfExists(filePath);
841
+ if (existing === content) {
842
+ return false;
843
+ }
844
+ await ensureDirectory(dirname(filePath));
845
+ await writeFile(filePath, content, "utf8");
846
+ return true;
847
+ };
848
+ const toPascalCaseIdentifier = (value) => {
849
+ const tokens = value.split(/[^A-Za-z0-9]+/).map((token) => token.trim()).filter((token) => token.length > 0);
850
+ if (tokens.length === 0) {
851
+ throw new Error(`Cannot derive loader function name from target: ${value}`);
852
+ }
853
+ const pascal = tokens.map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join("");
854
+ return /^[0-9]/.test(pascal) ? `Target${pascal}` : pascal;
855
+ };
856
+ const toRelativeImportSpecifier = (fromFile, toFile) => {
857
+ const rel = relative(dirname(fromFile), toFile).replace(/\\/g, "/");
858
+ return rel.startsWith(".") ? rel : `./${rel}`;
859
+ };
860
+ const buildGeneratedLoaderContent = (generatedLoaderFile, targets) => {
861
+ const targetBlocks = targets.map((target) => {
862
+ const specifier = JSON.stringify(
863
+ toRelativeImportSpecifier(generatedLoaderFile, target.outFile)
864
+ );
865
+ return `export const ${target.functionName} = async <T extends object>(
866
+ options?: TargetWasmLoadOptions
867
+ ): Promise<WasmInstance<T>> => {
868
+ const source = options?.url ?? new URL(${specifier}, import.meta.url);
869
+ return await loadWasm<T>(source, {
870
+ imports: options?.imports,
871
+ });
872
+ };`;
873
+ }).join("\n\n");
874
+ return `// Generated by emsdk-env. DO NOT EDIT.
875
+
876
+ export type WasmSource =
877
+ | string
878
+ | URL
879
+ | ArrayBuffer
880
+ | ArrayBufferView
881
+ | Response;
882
+
883
+ export interface WasmLoadOptions {
884
+ readonly imports?: WebAssembly.Imports;
885
+ }
886
+
887
+ export interface TargetWasmLoadOptions extends WasmLoadOptions {
888
+ readonly url?: string | URL;
889
+ }
890
+
891
+ export interface WasmInstance<T extends object> {
892
+ readonly exports: T;
893
+ readonly memory: WebAssembly.Memory;
894
+ readonly table: WebAssembly.Table | undefined;
895
+ readonly rawExports: WebAssembly.Exports;
896
+ readonly module: WebAssembly.Module;
897
+ readonly instance: WebAssembly.Instance;
898
+ readonly initialize: (() => unknown) | undefined;
899
+ readonly start: (() => unknown) | undefined;
900
+ }
901
+
902
+ const resolveWasmBytes = async (source: WasmSource): Promise<ArrayBuffer> => {
903
+ if (typeof Response !== 'undefined' && source instanceof Response) {
904
+ return await source.arrayBuffer();
905
+ }
906
+ if (source instanceof URL || typeof source === 'string') {
907
+ const response = await fetch(source);
908
+ if (!response.ok) {
909
+ throw new Error(\`Failed to fetch wasm: \${response.url}\`);
910
+ }
911
+ return await response.arrayBuffer();
912
+ }
913
+ if (ArrayBuffer.isView(source)) {
914
+ const view = new Uint8Array(
915
+ source.buffer,
916
+ source.byteOffset,
917
+ source.byteLength
918
+ );
919
+ return view.slice().buffer;
920
+ }
921
+ return source;
922
+ };
923
+
924
+ const getImportValue = (
925
+ imports: WebAssembly.Imports | undefined,
926
+ moduleName: string,
927
+ name: string
928
+ ) => {
929
+ const moduleImports = imports?.[moduleName];
930
+ if (!moduleImports || typeof moduleImports !== 'object') {
931
+ return undefined;
932
+ }
933
+ return (moduleImports as Record<string, unknown>)[name];
934
+ };
935
+
936
+ const resolveMemory = (
937
+ module: WebAssembly.Module,
938
+ instance: WebAssembly.Instance,
939
+ imports: WebAssembly.Imports | undefined
940
+ ) => {
941
+ let memory: WebAssembly.Memory | undefined;
942
+ for (const entry of WebAssembly.Module.exports(module)) {
943
+ if (entry.kind !== 'memory') {
944
+ continue;
945
+ }
946
+ if (memory) {
947
+ throw new Error('Multiple wasm memories are not supported.');
948
+ }
949
+ const value = instance.exports[entry.name];
950
+ if (!(value instanceof WebAssembly.Memory)) {
951
+ throw new Error(\`Export is not a WebAssembly.Memory: \${entry.name}\`);
952
+ }
953
+ memory = value;
954
+ }
955
+ if (memory) {
956
+ return memory;
957
+ }
958
+ for (const entry of WebAssembly.Module.imports(module)) {
959
+ if (entry.kind !== 'memory') {
960
+ continue;
961
+ }
962
+ if (memory) {
963
+ throw new Error('Multiple wasm memories are not supported.');
964
+ }
965
+ const value = getImportValue(imports, entry.module, entry.name);
966
+ if (!(value instanceof WebAssembly.Memory)) {
967
+ throw new Error(
968
+ \`Imported value is not a WebAssembly.Memory: \${entry.module}.\${entry.name}\`
969
+ );
970
+ }
971
+ memory = value;
972
+ }
973
+ if (!memory) {
974
+ throw new Error('WASM memory export/import was not resolved.');
975
+ }
976
+ return memory;
977
+ };
978
+
979
+ const resolveTable = (
980
+ module: WebAssembly.Module,
981
+ instance: WebAssembly.Instance,
982
+ imports: WebAssembly.Imports | undefined
983
+ ) => {
984
+ let table: WebAssembly.Table | undefined;
985
+ for (const entry of WebAssembly.Module.exports(module)) {
986
+ if (entry.kind !== 'table') {
987
+ continue;
988
+ }
989
+ if (table) {
990
+ throw new Error('Multiple wasm tables are not supported.');
991
+ }
992
+ const value = instance.exports[entry.name];
993
+ if (!(value instanceof WebAssembly.Table)) {
994
+ throw new Error(\`Export is not a WebAssembly.Table: \${entry.name}\`);
995
+ }
996
+ table = value;
997
+ }
998
+ if (table) {
999
+ return table;
1000
+ }
1001
+ for (const entry of WebAssembly.Module.imports(module)) {
1002
+ if (entry.kind !== 'table') {
1003
+ continue;
1004
+ }
1005
+ if (table) {
1006
+ throw new Error('Multiple wasm tables are not supported.');
1007
+ }
1008
+ const value = getImportValue(imports, entry.module, entry.name);
1009
+ if (!(value instanceof WebAssembly.Table)) {
1010
+ throw new Error(
1011
+ \`Imported value is not a WebAssembly.Table: \${entry.module}.\${entry.name}\`
1012
+ );
1013
+ }
1014
+ table = value;
1015
+ }
1016
+ return table;
1017
+ };
1018
+
1019
+ export const loadWasm = async <T extends object>(
1020
+ source: WasmSource,
1021
+ options?: WasmLoadOptions
1022
+ ): Promise<WasmInstance<T>> => {
1023
+ const bytes = await resolveWasmBytes(source);
1024
+ const module = await WebAssembly.compile(bytes);
1025
+ const instance = await WebAssembly.instantiate(module, options?.imports ?? {});
1026
+
1027
+ const functionExports: Record<string, (...args: unknown[]) => unknown> = {};
1028
+ for (const entry of WebAssembly.Module.exports(module)) {
1029
+ if (entry.kind !== 'function') {
1030
+ continue;
1031
+ }
1032
+ const value = instance.exports[entry.name];
1033
+ if (typeof value !== 'function') {
1034
+ throw new Error(\`Export is not a function: \${entry.name}\`);
1035
+ }
1036
+ functionExports[entry.name] = value as (...args: unknown[]) => unknown;
1037
+ }
1038
+
1039
+ const initialize =
1040
+ typeof instance.exports._initialize === 'function'
1041
+ ? (instance.exports._initialize as () => unknown)
1042
+ : undefined;
1043
+ const start =
1044
+ typeof instance.exports._start === 'function'
1045
+ ? (instance.exports._start as () => unknown)
1046
+ : undefined;
1047
+
1048
+ return {
1049
+ exports: functionExports as T,
1050
+ memory: resolveMemory(module, instance, options?.imports),
1051
+ table: resolveTable(module, instance, options?.imports),
1052
+ rawExports: instance.exports,
1053
+ module,
1054
+ instance,
1055
+ initialize,
1056
+ start,
1057
+ };
1058
+ };
1059
+
1060
+ ${targetBlocks}
1061
+ `;
1062
+ };
687
1063
  const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
688
1064
  const resolvePackageJsonPath = async (startPath, packageName) => {
689
1065
  let current = dirname(startPath);
@@ -784,6 +1160,42 @@ const resolveImportDirectories = async (rootDir, imports) => {
784
1160
  libDirs: dedupeValues(libDirs)
785
1161
  };
786
1162
  };
1163
+ const resolveGeneratedLoaderOutFile = (rootDir, env, generatedLoader) => {
1164
+ var _a;
1165
+ if (!(generatedLoader == null ? void 0 : generatedLoader.enable)) {
1166
+ return void 0;
1167
+ }
1168
+ const rawOutFile = expandPlaceholders(
1169
+ (_a = generatedLoader.outFile) != null ? _a : DEFAULT_GENERATED_LOADER_OUT_FILE,
1170
+ env,
1171
+ "generatedLoader.outFile"
1172
+ );
1173
+ return resolvePath(rootDir, rawOutFile);
1174
+ };
1175
+ const resolveGeneratedLoaderWatchDirs = (rootDir, srcDir, commonIncludeDirs, env, targetEntries, importIncludeDirs) => {
1176
+ const dirs = [srcDir, ...resolveIncludeDirs(commonIncludeDirs, env, rootDir)];
1177
+ for (const [targetName, target] of targetEntries) {
1178
+ if (!target.includeDirs || target.includeDirs.length === 0) {
1179
+ continue;
1180
+ }
1181
+ const targetEnv = {
1182
+ ...env,
1183
+ TARGET_NAME: targetName
1184
+ };
1185
+ dirs.push(...resolveIncludeDirs(target.includeDirs, targetEnv, rootDir));
1186
+ }
1187
+ dirs.push(...importIncludeDirs);
1188
+ return dedupeValues(dirs);
1189
+ };
1190
+ const validateGeneratedLoaderOutFile = (generatedLoaderFile, watchDirs) => {
1191
+ for (const dir of watchDirs) {
1192
+ if (isSubPath(dir, generatedLoaderFile)) {
1193
+ throw new Error(
1194
+ `generatedLoader.outFile must not be placed under watched directory: ${generatedLoaderFile}`
1195
+ );
1196
+ }
1197
+ }
1198
+ };
787
1199
  const buildCompileArgs = (options, includeDirs, defines, env, rootDir) => {
788
1200
  const resolvedOptions = expandArray(options, env, "options");
789
1201
  const includeArgs = resolveIncludeDirs(includeDirs, env, rootDir).map(
@@ -866,6 +1278,22 @@ const buildWasm = async (options) => {
866
1278
  const importIncludeDirs = importDirectories.includeDirs;
867
1279
  const importLibDirs = importDirectories.libDirs;
868
1280
  const linkLibDirs = dedupeValues([libDir, ...importLibDirs]);
1281
+ const generatedLoaderFile = resolveGeneratedLoaderOutFile(
1282
+ rootDir,
1283
+ envWithDirs,
1284
+ options.generatedLoader
1285
+ );
1286
+ if (generatedLoaderFile) {
1287
+ const watchDirs = resolveGeneratedLoaderWatchDirs(
1288
+ rootDir,
1289
+ srcDir,
1290
+ commonIncludeDirs,
1291
+ envWithDirs,
1292
+ targetEntries,
1293
+ importIncludeDirs
1294
+ );
1295
+ validateGeneratedLoaderOutFile(generatedLoaderFile, watchDirs);
1296
+ }
869
1297
  logger.debug(`Detected rootDir: '${rootDir}'`);
870
1298
  logger.debug(`Detected srcDir: '${srcDir}'`);
871
1299
  logger.debug(`Detected outDir: '${outDir}'`);
@@ -882,6 +1310,9 @@ const buildWasm = async (options) => {
882
1310
  logger.debug(
883
1311
  `Detected importLibDirs: [${importLibDirs.map((p) => `'${p}'`).join(",")}]`
884
1312
  );
1313
+ if (generatedLoaderFile) {
1314
+ logger.debug(`Detected generatedLoaderFile: '${generatedLoaderFile}'`);
1315
+ }
885
1316
  await ensureDirectory(outDir);
886
1317
  await ensureDirectory(libDir);
887
1318
  await ensureDirectory(buildDir);
@@ -904,6 +1335,7 @@ const buildWasm = async (options) => {
904
1335
  return wasmOptCommand;
905
1336
  };
906
1337
  const outFiles = {};
1338
+ let resultGeneratedLoaderFile;
907
1339
  const buildTargets = async (expectedType) => {
908
1340
  var _a2;
909
1341
  for (const [targetName, target] of targetEntries) {
@@ -917,6 +1349,11 @@ const buildWasm = async (options) => {
917
1349
  `linkOptions is not supported for archive target: ${targetName}`
918
1350
  );
919
1351
  }
1352
+ if (target.linkDirectives !== void 0) {
1353
+ throw new Error(
1354
+ `linkDirectives is not supported for archive target: ${targetName}`
1355
+ );
1356
+ }
920
1357
  if (target.exports !== void 0) {
921
1358
  throw new Error(
922
1359
  `exports is not supported for archive target: ${targetName}`
@@ -932,6 +1369,7 @@ const buildWasm = async (options) => {
932
1369
  ...ensureArray(common.linkOptions),
933
1370
  ...ensureArray(target.linkOptions)
934
1371
  ];
1372
+ const mergedLinkDirectives = targetType === "archive" ? {} : mergeLinkDirectives(common.linkDirectives, target.linkDirectives);
935
1373
  const mergedExports = targetType === "archive" ? [] : [...ensureArray(common.exports), ...ensureArray(target.exports)];
936
1374
  const wasmOptEnabled = targetType === "archive" ? false : resolveWasmOptEnabled(common.wasmOpt, target.wasmOpt);
937
1375
  const baseCompileOptions = [
@@ -991,7 +1429,12 @@ const buildWasm = async (options) => {
991
1429
  const targetBuildDir = resolve(buildRunDir, targetName);
992
1430
  await rm(targetBuildDir, { recursive: true, force: true });
993
1431
  await ensureDirectory(targetBuildDir);
994
- const resolvedLinkOptions = targetType === "archive" ? [] : expandArray(mergedLinkOptions, targetEnv, "linkOptions");
1432
+ const resolvedLinkDirectives = targetType === "archive" ? {} : resolveLinkDirectives(mergedLinkDirectives, targetEnv);
1433
+ const linkDirectiveArgs = buildLinkDirectiveFlags(resolvedLinkDirectives);
1434
+ const resolvedLinkOptions = targetType === "archive" ? [] : [
1435
+ ...linkDirectiveArgs,
1436
+ ...expandArray(mergedLinkOptions, targetEnv, "linkOptions")
1437
+ ];
995
1438
  const resolvedExports = targetType === "archive" ? [] : expandArray(mergedExports, targetEnv, "exports");
996
1439
  const exportArgs = buildExportFlags(resolvedExports);
997
1440
  const resolvedWasmOptArgs = wasmOptEnabled ? resolveWasmOptArgs(common.wasmOpt, target.wasmOpt, targetEnv) : [];
@@ -1003,7 +1446,6 @@ const buildWasm = async (options) => {
1003
1446
  rootDir
1004
1447
  );
1005
1448
  const groupCompileArgs = sourceGroups.map((group) => {
1006
- var _a3;
1007
1449
  const groupOptions = [
1008
1450
  ...baseCompileOptions,
1009
1451
  ...ensureArray(group == null ? void 0 : group.options)
@@ -1012,7 +1454,7 @@ const buildWasm = async (options) => {
1012
1454
  ...baseIncludeDirs,
1013
1455
  ...ensureArray(group == null ? void 0 : group.includeDirs)
1014
1456
  ];
1015
- const groupDefines = mergeDefines(baseDefines, (_a3 = group == null ? void 0 : group.defines) != null ? _a3 : {});
1457
+ const groupDefines = mergeDefines(baseDefines, group == null ? void 0 : group.defines);
1016
1458
  return buildCompileArgs(
1017
1459
  groupOptions,
1018
1460
  groupIncludeDirs,
@@ -1129,9 +1571,18 @@ const buildWasm = async (options) => {
1129
1571
  emsdkOptions.signal
1130
1572
  );
1131
1573
  if (wasmOptEnabled) {
1132
- const tempOutFile = `${resolvedOutFile}.opt`;
1133
- const wasmOptArgs = [
1574
+ const wasmOptInput = resolveWasmOptInputFile(
1134
1575
  resolvedOutFile,
1576
+ resolvedLinkOptions
1577
+ );
1578
+ if (!await pathExists(wasmOptInput)) {
1579
+ throw new Error(
1580
+ `wasm-opt enabled but wasm binary not found: ${wasmOptInput}`
1581
+ );
1582
+ }
1583
+ const tempOutFile = `${wasmOptInput}.opt`;
1584
+ const wasmOptArgs = [
1585
+ wasmOptInput,
1135
1586
  "-o",
1136
1587
  tempOutFile,
1137
1588
  ...resolvedWasmOptArgs
@@ -1146,8 +1597,8 @@ const buildWasm = async (options) => {
1146
1597
  buildEnv,
1147
1598
  emsdkOptions.signal
1148
1599
  );
1149
- await rm(resolvedOutFile, { force: true });
1150
- await rename(tempOutFile, resolvedOutFile);
1600
+ await rm(wasmOptInput, { force: true });
1601
+ await rename(tempOutFile, wasmOptInput);
1151
1602
  }
1152
1603
  }
1153
1604
  outFiles[targetName] = resolvedOutFile;
@@ -1156,6 +1607,40 @@ const buildWasm = async (options) => {
1156
1607
  try {
1157
1608
  await buildTargets("archive");
1158
1609
  await buildTargets("wasm");
1610
+ if (generatedLoaderFile) {
1611
+ const generatedTargets = [];
1612
+ const functionNames = /* @__PURE__ */ new Set();
1613
+ for (const [targetName, target] of targetEntries) {
1614
+ if (resolveTargetType(target.type) !== "wasm") {
1615
+ continue;
1616
+ }
1617
+ const outFile = outFiles[targetName];
1618
+ if (!outFile) {
1619
+ continue;
1620
+ }
1621
+ const functionName = `load${toPascalCaseIdentifier(targetName)}Wasm`;
1622
+ if (functionNames.has(functionName)) {
1623
+ throw new Error(
1624
+ `Generated loader function name collision: ${functionName}`
1625
+ );
1626
+ }
1627
+ functionNames.add(functionName);
1628
+ generatedTargets.push({
1629
+ targetName,
1630
+ functionName,
1631
+ outFile
1632
+ });
1633
+ }
1634
+ const content = buildGeneratedLoaderContent(
1635
+ generatedLoaderFile,
1636
+ generatedTargets
1637
+ );
1638
+ const didWrite = await writeTextIfChanged(generatedLoaderFile, content);
1639
+ logger.info(
1640
+ didWrite ? `Generated loader: ${relative(rootDir, generatedLoaderFile)}` : `Generated loader unchanged: ${relative(rootDir, generatedLoaderFile)}`
1641
+ );
1642
+ resultGeneratedLoaderFile = generatedLoaderFile;
1643
+ }
1159
1644
  } finally {
1160
1645
  if (cleanupBuildDir) {
1161
1646
  await rm(buildRunDir, { recursive: true, force: true });
@@ -1163,7 +1648,8 @@ const buildWasm = async (options) => {
1163
1648
  }
1164
1649
  return {
1165
1650
  emsdkRoot,
1166
- outFiles
1651
+ outFiles,
1652
+ ...resultGeneratedLoaderFile ? { generatedLoaderFile: resultGeneratedLoaderFile } : {}
1167
1653
  };
1168
1654
  };
1169
1655
  export {
@@ -1171,4 +1657,4 @@ export {
1171
1657
  createConsoleLogger as c,
1172
1658
  prepareEmsdk as p
1173
1659
  };
1174
- //# sourceMappingURL=build-Btgi1orl.js.map
1660
+ //# sourceMappingURL=build-BOZTStIM.js.map