emsdk-env 0.3.0 → 0.5.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.3.0
3
+ * version: 0.5.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: 4a03739b8c88111950fa216a7c10c7bfd65ea239
8
+ * git.commit.hash: f54983861d59656c8f39fd46d984c1b373100dde
9
9
  */
10
10
 
11
- import { mkdir, access, constants, mkdtemp, rename, rm } from "fs/promises";
11
+ import { mkdir, access, constants, mkdtemp, rename, rm, readFile } from "fs/promises";
12
12
  import { homedir, tmpdir } from "os";
13
13
  import { glob } from "glob";
14
- import { join, resolve, isAbsolute, relative } from "path";
14
+ import { join, resolve, isAbsolute, dirname, relative } from "path";
15
15
  import { spawn } from "child_process";
16
16
  const __NOOP_HANDLER = () => {
17
17
  };
@@ -501,6 +501,22 @@ const resolveEmccCommand = async (env, emsdkRoot) => {
501
501
  }
502
502
  return "emcc";
503
503
  };
504
+ const resolveEmarCommand = async (env, emsdkRoot) => {
505
+ if (env.EMAR) {
506
+ return env.EMAR;
507
+ }
508
+ if (env.EMSCRIPTEN) {
509
+ const candidate = join(env.EMSCRIPTEN, "emar");
510
+ if (await pathExists(candidate)) {
511
+ return candidate;
512
+ }
513
+ }
514
+ const fallback = join(emsdkRoot, "upstream", "emscripten", "emar");
515
+ if (await pathExists(fallback)) {
516
+ return fallback;
517
+ }
518
+ return "emar";
519
+ };
504
520
  const createConsoleLogger = (prefix) => {
505
521
  return {
506
522
  debug: (msg) => console.debug(`[${prefix}]: ${msg}`),
@@ -510,7 +526,11 @@ const createConsoleLogger = (prefix) => {
510
526
  };
511
527
  };
512
528
  const DEFAULT_WASM_SRC_DIR = "wasm";
529
+ const DEFAULT_WASM_INCLUDE_DIR = "include";
513
530
  const DEFAULT_WASM_OUT_DIR = join("src", "wasm");
531
+ const DEFAULT_WASM_LIB_DIR = "lib";
532
+ const DEFAULT_IMPORT_INCLUDE_DIR = "include";
533
+ const DEFAULT_IMPORT_LIB_DIR = "lib";
514
534
  const DEFAULT_WASM_BUILD_DIR = join(tmpdir(), "emsdk-env");
515
535
  const DEFAULT_EMSDK_TARGET_VERSION = "latest";
516
536
  let buildSequence = 0;
@@ -531,6 +551,7 @@ const createBuildId = () => {
531
551
  return `${timestamp}_${seq}_${process.pid}`;
532
552
  };
533
553
  const ensureArray = (value) => value != null ? value : [];
554
+ const resolveTargetType = (value) => value != null ? value : "wasm";
534
555
  const normalizePrepareOptions = (options) => {
535
556
  const { targetVersion, ...rest } = options != null ? options : {};
536
557
  return {
@@ -592,11 +613,11 @@ const createEnvForBuild = (baseEnv, overrides) => ({
592
613
  ...baseEnv,
593
614
  ...overrides
594
615
  });
595
- const resolveTargetOutFile = (targetName, targetOutFile, env, outDir) => {
616
+ const resolveTargetOutFile = (targetName, targetOutFile, env, baseDir, extension) => {
596
617
  if (targetOutFile) {
597
- return resolveOutFile(targetOutFile, env, outDir);
618
+ return resolveOutFile(targetOutFile, env, baseDir);
598
619
  }
599
- return resolve(outDir, `${targetName}.wasm`);
620
+ return resolve(baseDir, `${targetName}.${extension}`);
600
621
  };
601
622
  const resolveTargetSources = async (targetSources, env, srcDir) => {
602
623
  const patterns = targetSources && targetSources.length > 0 ? targetSources : [join(srcDir, "**", "*.c"), join(srcDir, "**", "*.cpp")];
@@ -621,6 +642,118 @@ const dedupeSources = (sources) => {
621
642
  }
622
643
  return deduped;
623
644
  };
645
+ const dedupeValues = (values) => {
646
+ const seen = /* @__PURE__ */ new Set();
647
+ const deduped = [];
648
+ for (const value of values) {
649
+ if (seen.has(value)) {
650
+ continue;
651
+ }
652
+ seen.add(value);
653
+ deduped.push(value);
654
+ }
655
+ return deduped;
656
+ };
657
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
658
+ const resolvePackageJsonPath = async (startPath, packageName) => {
659
+ let current = dirname(startPath);
660
+ for (; ; ) {
661
+ const candidate = join(current, "package.json");
662
+ if (await pathExists(candidate)) {
663
+ return candidate;
664
+ }
665
+ const parent = dirname(current);
666
+ if (parent === current) {
667
+ throw new Error(`package.json not found for import: ${packageName}`);
668
+ }
669
+ current = parent;
670
+ }
671
+ };
672
+ const loadPackageJson = async (packageJsonPath, packageName) => {
673
+ try {
674
+ const raw = await readFile(packageJsonPath, "utf8");
675
+ const parsed = JSON.parse(raw);
676
+ if (!isRecord(parsed)) {
677
+ throw new Error("package.json must be an object.");
678
+ }
679
+ return parsed;
680
+ } catch (error) {
681
+ const message = error instanceof Error ? error.message : String(error);
682
+ throw new Error(
683
+ `Failed to read package.json for import ${packageName}: ${message}`
684
+ );
685
+ }
686
+ };
687
+ const resolveImportPaths = async (resolver, packageName) => {
688
+ let resolvedEntry;
689
+ try {
690
+ resolvedEntry = resolver.resolve(packageName);
691
+ } catch (error) {
692
+ const message = error instanceof Error ? error.message : String(error);
693
+ throw new Error(`Failed to resolve import ${packageName}: ${message}`);
694
+ }
695
+ const packageJsonPath = await resolvePackageJsonPath(
696
+ resolvedEntry,
697
+ packageName
698
+ );
699
+ const packageRoot = dirname(packageJsonPath);
700
+ const packageJson = await loadPackageJson(packageJsonPath, packageName);
701
+ const emsdkConfigRaw = packageJson["emsdk-env"];
702
+ if (emsdkConfigRaw !== void 0 && !isRecord(emsdkConfigRaw)) {
703
+ throw new Error(
704
+ `Invalid emsdk-env config for import ${packageName}: expected an object.`
705
+ );
706
+ }
707
+ const includeRaw = isRecord(emsdkConfigRaw) ? emsdkConfigRaw.include : void 0;
708
+ if (includeRaw !== void 0 && typeof includeRaw !== "string") {
709
+ throw new Error(
710
+ `Invalid emsdk-env include for import ${packageName}: expected a string.`
711
+ );
712
+ }
713
+ const libRaw = isRecord(emsdkConfigRaw) ? emsdkConfigRaw.lib : void 0;
714
+ if (libRaw !== void 0 && typeof libRaw !== "string") {
715
+ throw new Error(
716
+ `Invalid emsdk-env lib for import ${packageName}: expected a string.`
717
+ );
718
+ }
719
+ const includeRel = includeRaw != null ? includeRaw : DEFAULT_IMPORT_INCLUDE_DIR;
720
+ const libRel = libRaw != null ? libRaw : DEFAULT_IMPORT_LIB_DIR;
721
+ const includeDir = resolve(packageRoot, includeRel);
722
+ const libDir = resolve(packageRoot, libRel);
723
+ const includeExists = await pathExists(includeDir);
724
+ const libExists = await pathExists(libDir);
725
+ if (!includeExists && !libExists) {
726
+ throw new Error(
727
+ `Import ${packageName} does not provide include or lib directories.`
728
+ );
729
+ }
730
+ return {
731
+ includeDir: includeExists ? includeDir : void 0,
732
+ libDir: libExists ? libDir : void 0
733
+ };
734
+ };
735
+ const resolveImportDirectories = async (rootDir, imports) => {
736
+ if (imports.length === 0) {
737
+ return { includeDirs: [], libDirs: [] };
738
+ }
739
+ const moduleApi = await import("node:module");
740
+ const resolver = moduleApi.createRequire(resolve(rootDir, "package.json"));
741
+ const includeDirs = [];
742
+ const libDirs = [];
743
+ for (const packageName of imports) {
744
+ const resolved = await resolveImportPaths(resolver, packageName);
745
+ if (resolved.includeDir) {
746
+ includeDirs.push(resolved.includeDir);
747
+ }
748
+ if (resolved.libDir) {
749
+ libDirs.push(resolved.libDir);
750
+ }
751
+ }
752
+ return {
753
+ includeDirs: dedupeValues(includeDirs),
754
+ libDirs: dedupeValues(libDirs)
755
+ };
756
+ };
624
757
  const buildCompileArgs = (options, includeDirs, defines, env, rootDir) => {
625
758
  const resolvedOptions = expandArray(options, env, "options");
626
759
  const includeArgs = resolveIncludeDirs(includeDirs, env, rootDir).map(
@@ -630,15 +763,15 @@ const buildCompileArgs = (options, includeDirs, defines, env, rootDir) => {
630
763
  return { resolvedOptions, includeArgs, defineArgs };
631
764
  };
632
765
  const buildWasm = async (options) => {
633
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
766
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
634
767
  if (!options) {
635
768
  throw new TypeError("options must be provided.");
636
769
  }
637
770
  if (!options.rule || !options.rule.targets) {
638
771
  throw new TypeError("rule targets must be provided.");
639
772
  }
640
- const targets = Object.entries(options.rule.targets);
641
- if (targets.length === 0) {
773
+ const targetEntries = Object.entries(options.rule.targets);
774
+ if (targetEntries.length === 0) {
642
775
  throw new TypeError("rule targets must not be empty.");
643
776
  }
644
777
  const logger = (_a = options.logger) != null ? _a : createConsoleLogger("emsdk-env");
@@ -655,57 +788,118 @@ const buildWasm = async (options) => {
655
788
  baseEnv,
656
789
  "srcDir"
657
790
  );
791
+ const rawIncludeDir = expandPlaceholders(
792
+ (_d = options.includeDir) != null ? _d : DEFAULT_WASM_INCLUDE_DIR,
793
+ baseEnv,
794
+ "includeDir"
795
+ );
658
796
  const rawOutDir = expandPlaceholders(
659
- (_d = options.outDir) != null ? _d : DEFAULT_WASM_OUT_DIR,
797
+ (_e = options.outDir) != null ? _e : DEFAULT_WASM_OUT_DIR,
660
798
  baseEnv,
661
799
  "outDir"
662
800
  );
801
+ const rawLibDir = expandPlaceholders(
802
+ (_f = options.libDir) != null ? _f : DEFAULT_WASM_LIB_DIR,
803
+ baseEnv,
804
+ "libDir"
805
+ );
663
806
  const rawBuildDir = expandPlaceholders(
664
- (_e = options.buildDir) != null ? _e : DEFAULT_WASM_BUILD_DIR,
807
+ (_g = options.buildDir) != null ? _g : DEFAULT_WASM_BUILD_DIR,
665
808
  baseEnv,
666
809
  "buildDir"
667
810
  );
668
811
  const srcDir = resolvePath(rootDir, rawSrcDir);
812
+ const includeDir = resolvePath(rootDir, rawIncludeDir);
669
813
  const outDir = resolvePath(rootDir, rawOutDir);
814
+ const libDir = resolvePath(rootDir, rawLibDir);
670
815
  const buildDir = resolvePath(rootDir, rawBuildDir);
671
816
  const buildId = createBuildId();
672
817
  const buildRunDir = resolve(buildDir, buildId);
673
- const cleanupBuildDir = (_f = options.cleanupBuildDir) != null ? _f : true;
674
- const parallel = (_g = options.parallel) != null ? _g : true;
818
+ const cleanupBuildDir = (_h = options.cleanupBuildDir) != null ? _h : true;
819
+ const parallel = (_i = options.parallel) != null ? _i : true;
675
820
  const envWithDirs = {
676
821
  ...emsdkEnv,
677
822
  ROOT: rootDir,
678
823
  SRC_DIR: srcDir,
824
+ INCLUDE_DIR: includeDir,
679
825
  OUT_DIR: outDir,
826
+ LIB_DIR: libDir,
680
827
  BUILD_DIR: buildDir
681
828
  };
682
829
  const emccCommand = await resolveEmccCommand(envWithDirs, emsdkRoot);
683
- const common = (_h = options.rule.common) != null ? _h : {};
830
+ const common = (_j = options.rule.common) != null ? _j : {};
831
+ const commonIncludeDirs = common.includeDirs === void 0 ? [includeDir] : common.includeDirs;
832
+ const importDirectories = await resolveImportDirectories(
833
+ rootDir,
834
+ ensureArray(options.imports)
835
+ );
836
+ const importIncludeDirs = importDirectories.includeDirs;
837
+ const importLibDirs = importDirectories.libDirs;
838
+ const linkLibDirs = dedupeValues([libDir, ...importLibDirs]);
839
+ logger.debug(`Detected rootDir: '${rootDir}'`);
840
+ logger.debug(`Detected srcDir: '${srcDir}'`);
841
+ logger.debug(`Detected outDir: '${outDir}'`);
842
+ logger.debug(`Detected libDir: '${libDir}'`);
843
+ logger.debug(`Detected buildDir: '${buildDir}'`);
844
+ logger.debug(`Detected buildId: '${buildId}'`);
845
+ logger.debug(`Detected buildRunDir: '${buildRunDir}'`);
846
+ logger.debug(`Detected cleanupBuildDir: ${cleanupBuildDir}`);
847
+ logger.debug(`Detected parallel: ${parallel}`);
848
+ logger.debug(`Detected emccCommand: '${emccCommand}'`);
849
+ logger.debug(
850
+ `Detected importIncludeDirs: [${importIncludeDirs.map((p) => `'${p}'`).join(",")}]`
851
+ );
852
+ logger.debug(
853
+ `Detected importLibDirs: [${importLibDirs.map((p) => `'${p}'`).join(",")}]`
854
+ );
684
855
  await ensureDirectory(outDir);
856
+ await ensureDirectory(libDir);
685
857
  await ensureDirectory(buildDir);
686
858
  await rm(buildRunDir, { recursive: true, force: true });
687
859
  await ensureDirectory(buildRunDir);
860
+ const hasArchiveTargets = targetEntries.some(
861
+ ([, target]) => resolveTargetType(target.type) === "archive"
862
+ );
863
+ const emarCommand = hasArchiveTargets ? await resolveEmarCommand(envWithDirs, emsdkRoot) : void 0;
864
+ if (emarCommand) {
865
+ logger.debug(`Detected emarCommand: '${emarCommand}'`);
866
+ }
688
867
  const outFiles = {};
689
- try {
690
- for (const [targetName, target] of targets) {
691
- const mergedLinkOptions = [
868
+ const buildTargets = async (expectedType) => {
869
+ var _a2;
870
+ for (const [targetName, target] of targetEntries) {
871
+ const targetType = resolveTargetType(target.type);
872
+ if (targetType !== expectedType) {
873
+ continue;
874
+ }
875
+ if (targetType === "archive") {
876
+ if (target.linkOptions !== void 0) {
877
+ throw new Error(
878
+ `linkOptions is not supported for archive target: ${targetName}`
879
+ );
880
+ }
881
+ if (target.exports !== void 0) {
882
+ throw new Error(
883
+ `exports is not supported for archive target: ${targetName}`
884
+ );
885
+ }
886
+ }
887
+ const mergedLinkOptions = targetType === "archive" ? [] : [
692
888
  ...ensureArray(common.linkOptions),
693
889
  ...ensureArray(target.linkOptions)
694
890
  ];
695
- const mergedExports = [
696
- ...ensureArray(common.exports),
697
- ...ensureArray(target.exports)
698
- ];
891
+ const mergedExports = targetType === "archive" ? [] : [...ensureArray(common.exports), ...ensureArray(target.exports)];
699
892
  const baseCompileOptions = [
700
893
  ...ensureArray(common.options),
701
894
  ...ensureArray(target.options)
702
895
  ];
703
896
  const baseIncludeDirs = [
704
- ...ensureArray(common.includeDirs),
705
- ...ensureArray(target.includeDirs)
897
+ ...ensureArray(commonIncludeDirs),
898
+ ...ensureArray(target.includeDirs),
899
+ ...importIncludeDirs
706
900
  ];
707
901
  const baseDefines = mergeDefines(common.defines, target.defines);
708
- const sourceGroups = (_i = target.sourceGroups) != null ? _i : [];
902
+ const sourceGroups = (_a2 = target.sourceGroups) != null ? _a2 : [];
709
903
  const targetEnv = {
710
904
  ...envWithDirs,
711
905
  TARGET_NAME: targetName
@@ -715,7 +909,8 @@ const buildWasm = async (options) => {
715
909
  targetName,
716
910
  target.outFile,
717
911
  targetEnv,
718
- outDir
912
+ targetType === "archive" ? libDir : outDir,
913
+ targetType === "archive" ? "a" : "wasm"
719
914
  );
720
915
  const sources = await resolveTargetSources(
721
916
  target.sources,
@@ -751,12 +946,8 @@ const buildWasm = async (options) => {
751
946
  const targetBuildDir = resolve(buildRunDir, targetName);
752
947
  await rm(targetBuildDir, { recursive: true, force: true });
753
948
  await ensureDirectory(targetBuildDir);
754
- const resolvedLinkOptions = expandArray(
755
- mergedLinkOptions,
756
- targetEnv,
757
- "linkOptions"
758
- );
759
- const resolvedExports = expandArray(mergedExports, targetEnv, "exports");
949
+ const resolvedLinkOptions = targetType === "archive" ? [] : expandArray(mergedLinkOptions, targetEnv, "linkOptions");
950
+ const resolvedExports = targetType === "archive" ? [] : expandArray(mergedExports, targetEnv, "exports");
760
951
  const exportArgs = buildExportFlags(resolvedExports);
761
952
  const baseCompileArgs = buildCompileArgs(
762
953
  baseCompileOptions,
@@ -766,7 +957,7 @@ const buildWasm = async (options) => {
766
957
  rootDir
767
958
  );
768
959
  const groupCompileArgs = sourceGroups.map((group) => {
769
- var _a2;
960
+ var _a3;
770
961
  const groupOptions = [
771
962
  ...baseCompileOptions,
772
963
  ...ensureArray(group == null ? void 0 : group.options)
@@ -775,7 +966,7 @@ const buildWasm = async (options) => {
775
966
  ...baseIncludeDirs,
776
967
  ...ensureArray(group == null ? void 0 : group.includeDirs)
777
968
  ];
778
- const groupDefines = mergeDefines(baseDefines, (_a2 = group == null ? void 0 : group.defines) != null ? _a2 : {});
969
+ const groupDefines = mergeDefines(baseDefines, (_a3 = group == null ? void 0 : group.defines) != null ? _a3 : {});
779
970
  return buildCompileArgs(
780
971
  groupOptions,
781
972
  groupIncludeDirs,
@@ -784,7 +975,6 @@ const buildWasm = async (options) => {
784
975
  rootDir
785
976
  );
786
977
  });
787
- logger.info(`Compiling target: ${targetName}`);
788
978
  const compileSource = async (source, args, groupIndex) => {
789
979
  const objectName = toSafeObjectName(rootDir, source, groupIndex);
790
980
  const outputObject = resolve(targetBuildDir, `${objectName}.o`);
@@ -797,6 +987,8 @@ const buildWasm = async (options) => {
797
987
  ...args.includeArgs,
798
988
  ...args.defineArgs
799
989
  ];
990
+ const sourcePath = relative(rootDir, source);
991
+ logger.info(`Compiling source: ${sourcePath} --> $tmp/${objectName}.o`);
800
992
  logger.debug(`emcc ${compileArgs.join(" ")}`);
801
993
  await runCommandWithEnv(
802
994
  emccCommand,
@@ -837,6 +1029,9 @@ const buildWasm = async (options) => {
837
1029
  groupIndex: void 0
838
1030
  });
839
1031
  }
1032
+ logger.info(
1033
+ parallel ? `Building target: '${targetName}' [${compileJobs.length} files, in parallel]` : `Building target: '${targetName}' [${compileJobs.length} files]`
1034
+ );
840
1035
  for (let index = 0; index < groupSources.length; index += 1) {
841
1036
  const sourcesInGroup = groupSources[index];
842
1037
  if (!sourcesInGroup) {
@@ -855,24 +1050,45 @@ const buildWasm = async (options) => {
855
1050
  (job) => compileSource(job.source, job.args, job.groupIndex)
856
1051
  )
857
1052
  ) : await buildObjectsSequential();
858
- logger.info(`Linking target: ${targetName}`);
859
- const linkArgs = [
860
- ...objectFiles,
861
- "-o",
862
- resolvedOutFile,
863
- ...resolvedLinkOptions,
864
- ...exportArgs
865
- ];
866
- logger.debug(`emcc ${linkArgs.join(" ")}`);
867
- await runCommandWithEnv(
868
- emccCommand,
869
- linkArgs,
870
- rootDir,
871
- buildEnv,
872
- emsdkOptions.signal
873
- );
1053
+ if (targetType === "archive") {
1054
+ if (!emarCommand) {
1055
+ throw new Error("emar command is required for archive targets.");
1056
+ }
1057
+ logger.info(`Archiving target: ${targetName}.a`);
1058
+ const archiveArgs = ["rcs", resolvedOutFile, ...objectFiles];
1059
+ logger.debug(`emar ${archiveArgs.join(" ")}`);
1060
+ await runCommandWithEnv(
1061
+ emarCommand,
1062
+ archiveArgs,
1063
+ rootDir,
1064
+ buildEnv,
1065
+ emsdkOptions.signal
1066
+ );
1067
+ } else {
1068
+ logger.info(`Linking target: ${targetName}.wasm`);
1069
+ const linkArgs = [
1070
+ ...objectFiles,
1071
+ "-o",
1072
+ resolvedOutFile,
1073
+ ...linkLibDirs.map((dir) => `-L${dir}`),
1074
+ ...resolvedLinkOptions,
1075
+ ...exportArgs
1076
+ ];
1077
+ logger.debug(`emcc ${linkArgs.join(" ")}`);
1078
+ await runCommandWithEnv(
1079
+ emccCommand,
1080
+ linkArgs,
1081
+ rootDir,
1082
+ buildEnv,
1083
+ emsdkOptions.signal
1084
+ );
1085
+ }
874
1086
  outFiles[targetName] = resolvedOutFile;
875
1087
  }
1088
+ };
1089
+ try {
1090
+ await buildTargets("archive");
1091
+ await buildTargets("wasm");
876
1092
  } finally {
877
1093
  if (cleanupBuildDir) {
878
1094
  await rm(buildRunDir, { recursive: true, force: true });
@@ -885,6 +1101,7 @@ const buildWasm = async (options) => {
885
1101
  };
886
1102
  export {
887
1103
  buildWasm as b,
1104
+ createConsoleLogger as c,
888
1105
  prepareEmsdk as p
889
1106
  };
890
- //# sourceMappingURL=build-CYHeaOdc.js.map
1107
+ //# sourceMappingURL=build-EqOBCaAv.js.map