emsdk-env 0.1.0 → 0.2.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.
- package/README.md +90 -2
- package/dist/build-oenTJc-4.cjs +782 -0
- package/dist/build-oenTJc-4.cjs.map +1 -0
- package/dist/build-qp0IEBnb.js +783 -0
- package/dist/build-qp0IEBnb.js.map +1 -0
- package/dist/index.cjs +5 -298
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +76 -5
- package/dist/index.mjs +5 -298
- package/dist/index.mjs.map +1 -1
- package/dist/vite.cjs +686 -9
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.ts +40 -6
- package/dist/vite.mjs +686 -9
- package/dist/vite.mjs.map +1 -1
- package/images/emsdk-env-120.png +0 -0
- package/package.json +15 -11
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* name: emsdk-env
|
|
3
|
+
* version: 0.2.0
|
|
4
|
+
* description: Emscripten environment builder
|
|
5
|
+
* author: Kouji Matsui (@kekyo@mi.kekyo.net)
|
|
6
|
+
* license: MIT
|
|
7
|
+
* repository.url: https://github.com/kekyo/emsdk-env
|
|
8
|
+
* git.commit.hash: e7a4478d462904c71937847aef2de1f460ffc0d5
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
const promises = require("fs/promises");
|
|
13
|
+
const os = require("os");
|
|
14
|
+
const glob = require("glob");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const child_process = require("child_process");
|
|
17
|
+
const __NOOP_HANDLER = () => {
|
|
18
|
+
};
|
|
19
|
+
const __NOOP_RELEASABLE = {
|
|
20
|
+
release: __NOOP_HANDLER,
|
|
21
|
+
[Symbol.dispose]: __NOOP_HANDLER
|
|
22
|
+
};
|
|
23
|
+
const toAbortError = (reason) => {
|
|
24
|
+
if (reason instanceof Error) {
|
|
25
|
+
return reason;
|
|
26
|
+
}
|
|
27
|
+
if (typeof reason === "string") {
|
|
28
|
+
return new Error(reason);
|
|
29
|
+
}
|
|
30
|
+
return new Error("Operation aborted");
|
|
31
|
+
};
|
|
32
|
+
const onAbort = (signal, callback) => {
|
|
33
|
+
if (!signal) {
|
|
34
|
+
return __NOOP_RELEASABLE;
|
|
35
|
+
}
|
|
36
|
+
if (signal.aborted) {
|
|
37
|
+
try {
|
|
38
|
+
callback(toAbortError(signal.reason));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn("AbortHook callback error: ", error);
|
|
41
|
+
}
|
|
42
|
+
return __NOOP_RELEASABLE;
|
|
43
|
+
}
|
|
44
|
+
let abortHandler;
|
|
45
|
+
abortHandler = () => {
|
|
46
|
+
if (abortHandler) {
|
|
47
|
+
const reason = signal.reason;
|
|
48
|
+
signal.removeEventListener("abort", abortHandler);
|
|
49
|
+
abortHandler = void 0;
|
|
50
|
+
try {
|
|
51
|
+
callback(toAbortError(reason));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn("AbortHook callback error: ", error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const release = () => {
|
|
58
|
+
if (abortHandler) {
|
|
59
|
+
signal.removeEventListener("abort", abortHandler);
|
|
60
|
+
abortHandler = void 0;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
64
|
+
const handle = {
|
|
65
|
+
release,
|
|
66
|
+
[Symbol.dispose]: release
|
|
67
|
+
};
|
|
68
|
+
return handle;
|
|
69
|
+
};
|
|
70
|
+
const defer = (fn) => {
|
|
71
|
+
if (typeof setImmediate === "function") {
|
|
72
|
+
setImmediate(fn);
|
|
73
|
+
} else {
|
|
74
|
+
setTimeout(fn, 0);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const ABORTED_ERROR$2 = () => new Error("Lock acquisition was aborted");
|
|
78
|
+
const createLockHandle = (releaseCallback) => {
|
|
79
|
+
let isActive = true;
|
|
80
|
+
const release = () => {
|
|
81
|
+
if (!isActive) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
isActive = false;
|
|
85
|
+
releaseCallback();
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
get isActive() {
|
|
89
|
+
return isActive;
|
|
90
|
+
},
|
|
91
|
+
release,
|
|
92
|
+
[Symbol.dispose]: release
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const createMutex = (maxConsecutiveCalls = 20) => {
|
|
96
|
+
let isLocked = false;
|
|
97
|
+
const queue = [];
|
|
98
|
+
let count = 0;
|
|
99
|
+
const processQueue = () => {
|
|
100
|
+
var _a;
|
|
101
|
+
if (isLocked || queue.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const item = queue.shift();
|
|
105
|
+
if ((_a = item.signal) == null ? void 0 : _a.aborted) {
|
|
106
|
+
item.reject(ABORTED_ERROR$2());
|
|
107
|
+
scheduleNextProcess();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
isLocked = true;
|
|
111
|
+
const lockHandle = createLockHandle(releaseLock);
|
|
112
|
+
item.resolve(lockHandle);
|
|
113
|
+
};
|
|
114
|
+
const scheduleNextProcess = () => {
|
|
115
|
+
count++;
|
|
116
|
+
if (count >= maxConsecutiveCalls) {
|
|
117
|
+
count = 0;
|
|
118
|
+
defer(processQueue);
|
|
119
|
+
} else {
|
|
120
|
+
processQueue();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const releaseLock = () => {
|
|
124
|
+
if (!isLocked) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
isLocked = false;
|
|
128
|
+
scheduleNextProcess();
|
|
129
|
+
};
|
|
130
|
+
const removeFromQueue = (item) => {
|
|
131
|
+
const index = queue.indexOf(item);
|
|
132
|
+
if (index !== -1) {
|
|
133
|
+
queue.splice(index, 1);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const lock = async (signal) => {
|
|
137
|
+
if (signal) {
|
|
138
|
+
if (signal.aborted) {
|
|
139
|
+
throw ABORTED_ERROR$2();
|
|
140
|
+
}
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const queueItem = {
|
|
143
|
+
resolve: void 0,
|
|
144
|
+
reject: void 0,
|
|
145
|
+
signal
|
|
146
|
+
};
|
|
147
|
+
const abortHandle = onAbort(signal, () => {
|
|
148
|
+
removeFromQueue(queueItem);
|
|
149
|
+
reject(ABORTED_ERROR$2());
|
|
150
|
+
});
|
|
151
|
+
queueItem.resolve = (handle) => {
|
|
152
|
+
abortHandle.release();
|
|
153
|
+
resolve(handle);
|
|
154
|
+
};
|
|
155
|
+
queueItem.reject = (error) => {
|
|
156
|
+
abortHandle.release();
|
|
157
|
+
reject(error);
|
|
158
|
+
};
|
|
159
|
+
queue.push(queueItem);
|
|
160
|
+
processQueue();
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
queue.push({
|
|
165
|
+
resolve,
|
|
166
|
+
reject
|
|
167
|
+
});
|
|
168
|
+
processQueue();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const result = {
|
|
173
|
+
lock,
|
|
174
|
+
waiter: {
|
|
175
|
+
wait: lock
|
|
176
|
+
},
|
|
177
|
+
get isLocked() {
|
|
178
|
+
return isLocked;
|
|
179
|
+
},
|
|
180
|
+
get pendingCount() {
|
|
181
|
+
return queue.length;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return result;
|
|
185
|
+
};
|
|
186
|
+
const createAbortError = () => {
|
|
187
|
+
const error = new Error("The operation was aborted.");
|
|
188
|
+
error.name = "AbortError";
|
|
189
|
+
return error;
|
|
190
|
+
};
|
|
191
|
+
const runCommand = async (command, args, cwd, signal) => {
|
|
192
|
+
signal == null ? void 0 : signal.throwIfAborted();
|
|
193
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
194
|
+
const child = child_process.spawn(command, args, {
|
|
195
|
+
cwd,
|
|
196
|
+
stdio: "inherit"
|
|
197
|
+
});
|
|
198
|
+
let settled = false;
|
|
199
|
+
const onAbort2 = () => {
|
|
200
|
+
if (settled) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
settled = true;
|
|
204
|
+
child.kill();
|
|
205
|
+
rejectPromise(createAbortError());
|
|
206
|
+
};
|
|
207
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort2, { once: true });
|
|
208
|
+
const cleanup = () => {
|
|
209
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort2);
|
|
210
|
+
};
|
|
211
|
+
child.once("error", (error) => {
|
|
212
|
+
if (settled) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
settled = true;
|
|
216
|
+
cleanup();
|
|
217
|
+
rejectPromise(error);
|
|
218
|
+
});
|
|
219
|
+
child.once("close", (code) => {
|
|
220
|
+
if (settled) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
settled = true;
|
|
224
|
+
cleanup();
|
|
225
|
+
if (code === 0) {
|
|
226
|
+
resolvePromise();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
rejectPromise(
|
|
230
|
+
new Error(
|
|
231
|
+
`Command failed: ${command} ${args.join(" ")} (exit code ${code})`
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
const runCommandWithEnv = async (command, args, cwd, env, signal) => {
|
|
238
|
+
signal == null ? void 0 : signal.throwIfAborted();
|
|
239
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
240
|
+
const child = child_process.spawn(command, args, {
|
|
241
|
+
cwd,
|
|
242
|
+
env,
|
|
243
|
+
stdio: "inherit"
|
|
244
|
+
});
|
|
245
|
+
let settled = false;
|
|
246
|
+
const onAbort2 = () => {
|
|
247
|
+
if (settled) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
settled = true;
|
|
251
|
+
child.kill();
|
|
252
|
+
rejectPromise(createAbortError());
|
|
253
|
+
};
|
|
254
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort2, { once: true });
|
|
255
|
+
const cleanup = () => {
|
|
256
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort2);
|
|
257
|
+
};
|
|
258
|
+
child.once("error", (error) => {
|
|
259
|
+
if (settled) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
settled = true;
|
|
263
|
+
cleanup();
|
|
264
|
+
rejectPromise(error);
|
|
265
|
+
});
|
|
266
|
+
child.once("close", (code) => {
|
|
267
|
+
if (settled) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
settled = true;
|
|
271
|
+
cleanup();
|
|
272
|
+
if (code === 0) {
|
|
273
|
+
resolvePromise();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
rejectPromise(
|
|
277
|
+
new Error(
|
|
278
|
+
`Command failed: ${command} ${args.join(" ")} (exit code ${code})`
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
const runCommandCapture = async (command, args, cwd, signal) => {
|
|
285
|
+
signal == null ? void 0 : signal.throwIfAborted();
|
|
286
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
287
|
+
const stdoutChunks = [];
|
|
288
|
+
const stderrChunks = [];
|
|
289
|
+
const child = child_process.spawn(command, args, {
|
|
290
|
+
cwd,
|
|
291
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
292
|
+
});
|
|
293
|
+
let settled = false;
|
|
294
|
+
const onAbort2 = () => {
|
|
295
|
+
if (settled) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
settled = true;
|
|
299
|
+
child.kill();
|
|
300
|
+
rejectPromise(createAbortError());
|
|
301
|
+
};
|
|
302
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort2, { once: true });
|
|
303
|
+
const cleanup = () => {
|
|
304
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort2);
|
|
305
|
+
};
|
|
306
|
+
child.stdout.on("data", (chunk) => {
|
|
307
|
+
stdoutChunks.push(chunk);
|
|
308
|
+
});
|
|
309
|
+
child.stderr.on("data", (chunk) => {
|
|
310
|
+
stderrChunks.push(chunk);
|
|
311
|
+
});
|
|
312
|
+
child.once("error", (error) => {
|
|
313
|
+
if (settled) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
settled = true;
|
|
317
|
+
cleanup();
|
|
318
|
+
rejectPromise(error);
|
|
319
|
+
});
|
|
320
|
+
child.once("close", (code) => {
|
|
321
|
+
if (settled) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
settled = true;
|
|
325
|
+
cleanup();
|
|
326
|
+
if (code === 0) {
|
|
327
|
+
resolvePromise(Buffer.concat(stdoutChunks));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const stderrText = Buffer.concat(stderrChunks).toString("utf8");
|
|
331
|
+
rejectPromise(
|
|
332
|
+
new Error(
|
|
333
|
+
`Command failed: ${command} ${args.join(" ")} (exit code ${code})${stderrText ? `
|
|
334
|
+
${stderrText}` : ""}`
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
const pathExists = async (targetPath) => {
|
|
341
|
+
try {
|
|
342
|
+
await promises.access(targetPath, promises.constants.F_OK);
|
|
343
|
+
return true;
|
|
344
|
+
} catch (e) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const ensureDirectory = async (targetPath) => {
|
|
349
|
+
await promises.mkdir(targetPath, { recursive: true });
|
|
350
|
+
};
|
|
351
|
+
const DEFAULT_REPO_URL = "https://github.com/emscripten-core/emsdk.git";
|
|
352
|
+
const DEFAULT_GIT_REF = "main";
|
|
353
|
+
const DEFAULT_CACHE_DIR = path.join(os.homedir(), ".cache", "emsdk-env");
|
|
354
|
+
const DEFAULT_TARGET_VERSION = "latest";
|
|
355
|
+
const versionMutexes = /* @__PURE__ */ new Map();
|
|
356
|
+
const getVersionMutex = (key) => {
|
|
357
|
+
let mutex = versionMutexes.get(key);
|
|
358
|
+
if (!mutex) {
|
|
359
|
+
mutex = createMutex();
|
|
360
|
+
versionMutexes.set(key, mutex);
|
|
361
|
+
}
|
|
362
|
+
return mutex;
|
|
363
|
+
};
|
|
364
|
+
const ensureNonEmpty = (value, label) => {
|
|
365
|
+
if (value.trim().length === 0) {
|
|
366
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const sanitizeSegment = (value) => {
|
|
370
|
+
const trimmed = value.trim();
|
|
371
|
+
ensureNonEmpty(trimmed, "targetVersion");
|
|
372
|
+
const sanitized = trimmed.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
373
|
+
if (sanitized === "." || sanitized === ".." || sanitized.length === 0) {
|
|
374
|
+
throw new TypeError("targetVersion results in an unsafe path segment.");
|
|
375
|
+
}
|
|
376
|
+
return sanitized;
|
|
377
|
+
};
|
|
378
|
+
const resolveEmsdkCommand = () => process.platform === "win32" ? "emsdk.bat" : "./emsdk";
|
|
379
|
+
const runEmsdk = async (repoDir, args, signal) => {
|
|
380
|
+
if (process.platform === "win32") {
|
|
381
|
+
await runCommand("cmd", ["/c", "emsdk.bat", ...args], repoDir, signal);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
await runCommand(resolveEmsdkCommand(), args, repoDir, signal);
|
|
385
|
+
};
|
|
386
|
+
const runGitClone = async (gitPath, repoUrl, targetDir, cwd, signal) => {
|
|
387
|
+
await runCommand(
|
|
388
|
+
gitPath,
|
|
389
|
+
["clone", repoUrl, targetDir, "--depth", "1", "--branch", DEFAULT_GIT_REF],
|
|
390
|
+
cwd,
|
|
391
|
+
signal
|
|
392
|
+
);
|
|
393
|
+
};
|
|
394
|
+
const isAlreadyExistsError = (error) => error instanceof Error && "code" in error && error.code !== void 0 && ["EEXIST", "ENOTEMPTY", "EISDIR"].includes(
|
|
395
|
+
String(error.code)
|
|
396
|
+
);
|
|
397
|
+
const prepareEmsdk = async (options) => {
|
|
398
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
399
|
+
if (!options) {
|
|
400
|
+
throw new TypeError("options must be provided.");
|
|
401
|
+
}
|
|
402
|
+
if (options.targetVersion !== void 0 && typeof options.targetVersion !== "string") {
|
|
403
|
+
throw new TypeError("targetVersion must be a string.");
|
|
404
|
+
}
|
|
405
|
+
const targetVersion = (_a = options.targetVersion) != null ? _a : DEFAULT_TARGET_VERSION;
|
|
406
|
+
ensureNonEmpty(targetVersion, "targetVersion");
|
|
407
|
+
(_b = options.signal) == null ? void 0 : _b.throwIfAborted();
|
|
408
|
+
const cacheDir = path.resolve((_c = options.cacheDir) != null ? _c : DEFAULT_CACHE_DIR);
|
|
409
|
+
const repoUrl = (_d = options.repoUrl) != null ? _d : DEFAULT_REPO_URL;
|
|
410
|
+
const gitPath = (_e = options.gitPath) != null ? _e : "git";
|
|
411
|
+
const versionDir = sanitizeSegment(targetVersion);
|
|
412
|
+
const finalDir = path.resolve(cacheDir, versionDir);
|
|
413
|
+
const mutex = getVersionMutex(finalDir);
|
|
414
|
+
const lock = await mutex.lock(options.signal);
|
|
415
|
+
try {
|
|
416
|
+
(_f = options.signal) == null ? void 0 : _f.throwIfAborted();
|
|
417
|
+
if (await pathExists(finalDir)) {
|
|
418
|
+
return finalDir;
|
|
419
|
+
}
|
|
420
|
+
await ensureDirectory(cacheDir);
|
|
421
|
+
const tempRoot = await promises.mkdtemp(path.join(cacheDir, ".tmp-"));
|
|
422
|
+
const tempRepoDir = path.join(tempRoot, "emsdk");
|
|
423
|
+
try {
|
|
424
|
+
await runGitClone(
|
|
425
|
+
gitPath,
|
|
426
|
+
repoUrl,
|
|
427
|
+
tempRepoDir,
|
|
428
|
+
cacheDir,
|
|
429
|
+
options.signal
|
|
430
|
+
);
|
|
431
|
+
(_g = options.signal) == null ? void 0 : _g.throwIfAborted();
|
|
432
|
+
await runEmsdk(tempRepoDir, ["install", targetVersion], options.signal);
|
|
433
|
+
try {
|
|
434
|
+
await promises.rename(tempRepoDir, finalDir);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (isAlreadyExistsError(error)) {
|
|
437
|
+
return finalDir;
|
|
438
|
+
}
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
} finally {
|
|
442
|
+
await promises.rm(tempRoot, { recursive: true, force: true });
|
|
443
|
+
}
|
|
444
|
+
(_h = options.signal) == null ? void 0 : _h.throwIfAborted();
|
|
445
|
+
await runEmsdk(finalDir, ["activate", targetVersion], options.signal);
|
|
446
|
+
return finalDir;
|
|
447
|
+
} finally {
|
|
448
|
+
lock.release();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const shellQuote = (value) => `'${String(value).replace(/'/g, `'"'"'`)}'`;
|
|
452
|
+
const parseEnvBuffer = (buffer) => {
|
|
453
|
+
const entries = buffer.toString("utf8").split("\0");
|
|
454
|
+
const env = {};
|
|
455
|
+
for (const entry of entries) {
|
|
456
|
+
if (!entry) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const delimiterIndex = entry.indexOf("=");
|
|
460
|
+
if (delimiterIndex <= 0) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const key = entry.slice(0, delimiterIndex);
|
|
464
|
+
const value = entry.slice(delimiterIndex + 1);
|
|
465
|
+
env[key] = value;
|
|
466
|
+
}
|
|
467
|
+
return env;
|
|
468
|
+
};
|
|
469
|
+
const loadEmsdkEnv = async (emsdkRoot, logger, signal) => {
|
|
470
|
+
if (process.platform === "win32") {
|
|
471
|
+
throw new Error(
|
|
472
|
+
"Emscripten environment extraction on Windows is not implemented yet."
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
const envScript = path.resolve(emsdkRoot, "emsdk_env.sh");
|
|
476
|
+
if (!await pathExists(envScript)) {
|
|
477
|
+
throw new Error(`emsdk_env.sh not found: ${envScript}`);
|
|
478
|
+
}
|
|
479
|
+
const command = `. ${shellQuote(envScript)} >/dev/null 2>&1; env -0`;
|
|
480
|
+
logger.debug(`Loading emsdk environment: ${envScript}`);
|
|
481
|
+
const output = await runCommandCapture(
|
|
482
|
+
"bash",
|
|
483
|
+
["-lc", command],
|
|
484
|
+
emsdkRoot,
|
|
485
|
+
signal
|
|
486
|
+
);
|
|
487
|
+
return parseEnvBuffer(output);
|
|
488
|
+
};
|
|
489
|
+
const resolveEmccCommand = async (env, emsdkRoot) => {
|
|
490
|
+
if (env.EMCC) {
|
|
491
|
+
return env.EMCC;
|
|
492
|
+
}
|
|
493
|
+
if (env.EMSCRIPTEN) {
|
|
494
|
+
const candidate = path.join(env.EMSCRIPTEN, "emcc");
|
|
495
|
+
if (await pathExists(candidate)) {
|
|
496
|
+
return candidate;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const fallback = path.join(emsdkRoot, "upstream", "emscripten", "emcc");
|
|
500
|
+
if (await pathExists(fallback)) {
|
|
501
|
+
return fallback;
|
|
502
|
+
}
|
|
503
|
+
return "emcc";
|
|
504
|
+
};
|
|
505
|
+
const createConsoleLogger = (prefix) => {
|
|
506
|
+
return {
|
|
507
|
+
debug: (msg) => console.debug(`[${prefix}]: ${msg}`),
|
|
508
|
+
info: (msg) => console.info(`[${prefix}]: ${msg}`),
|
|
509
|
+
warn: (msg) => console.warn(`[${prefix}]: ${msg}`),
|
|
510
|
+
error: (msg) => console.error(`[${prefix}]: ${msg}`)
|
|
511
|
+
};
|
|
512
|
+
};
|
|
513
|
+
const DEFAULT_WASM_SRC_DIR = "wasm";
|
|
514
|
+
const DEFAULT_WASM_OUT_DIR = path.join("src", "wasm");
|
|
515
|
+
const DEFAULT_WASM_BUILD_DIR = path.join(os.tmpdir(), "emsdk-env");
|
|
516
|
+
const DEFAULT_EMSDK_TARGET_VERSION = "latest";
|
|
517
|
+
let buildSequence = 0;
|
|
518
|
+
const padNumber = (value, length = 2) => String(value).padStart(length, "0");
|
|
519
|
+
const formatTimestamp = (date) => {
|
|
520
|
+
const year = date.getFullYear();
|
|
521
|
+
const month = padNumber(date.getMonth() + 1);
|
|
522
|
+
const day = padNumber(date.getDate());
|
|
523
|
+
const hour = padNumber(date.getHours());
|
|
524
|
+
const minute = padNumber(date.getMinutes());
|
|
525
|
+
const second = padNumber(date.getSeconds());
|
|
526
|
+
return `${year}${month}${day}_${hour}${minute}${second}`;
|
|
527
|
+
};
|
|
528
|
+
const createBuildId = () => {
|
|
529
|
+
buildSequence += 1;
|
|
530
|
+
const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
|
|
531
|
+
const seq = String(buildSequence).padStart(4, "0");
|
|
532
|
+
return `${timestamp}_${seq}_${process.pid}`;
|
|
533
|
+
};
|
|
534
|
+
const ensureArray = (value) => value ? [...value] : [];
|
|
535
|
+
const normalizePrepareOptions = (options) => {
|
|
536
|
+
const { targetVersion, ...rest } = options != null ? options : {};
|
|
537
|
+
return {
|
|
538
|
+
targetVersion: targetVersion != null ? targetVersion : DEFAULT_EMSDK_TARGET_VERSION,
|
|
539
|
+
...rest
|
|
540
|
+
};
|
|
541
|
+
};
|
|
542
|
+
const mergeDefines = (common, target) => ({
|
|
543
|
+
...common != null ? common : {},
|
|
544
|
+
...target != null ? target : {}
|
|
545
|
+
});
|
|
546
|
+
const resolvePath = (rootDir, value) => path.isAbsolute(value) ? value : path.resolve(rootDir, value);
|
|
547
|
+
const expandPlaceholders = (value, env, label) => value.replace(/\{([A-Z0-9_]+)\}/g, (_match, key) => {
|
|
548
|
+
const replacement = env[key];
|
|
549
|
+
if (replacement === void 0) {
|
|
550
|
+
throw new Error(`Unknown placeholder {${key}} in ${label}.`);
|
|
551
|
+
}
|
|
552
|
+
return replacement;
|
|
553
|
+
});
|
|
554
|
+
const expandArray = (values, env, label) => values.map((value) => expandPlaceholders(value, env, label));
|
|
555
|
+
const resolveDefines = (defines, env) => {
|
|
556
|
+
const resolved = {};
|
|
557
|
+
for (const [key, value] of Object.entries(defines)) {
|
|
558
|
+
if (typeof value === "string") {
|
|
559
|
+
resolved[key] = expandPlaceholders(value, env, `defines.${key}`);
|
|
560
|
+
} else {
|
|
561
|
+
resolved[key] = value;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return resolved;
|
|
565
|
+
};
|
|
566
|
+
const resolveIncludeDirs = (includeDirs, env, rootDir) => {
|
|
567
|
+
const expanded = expandArray(includeDirs, env, "includeDirs");
|
|
568
|
+
return expanded.map((value) => resolvePath(rootDir, value));
|
|
569
|
+
};
|
|
570
|
+
const resolveOutFile = (outFile, env, outDir) => {
|
|
571
|
+
const expanded = expandPlaceholders(outFile, env, "outFile");
|
|
572
|
+
return resolvePath(outDir, expanded);
|
|
573
|
+
};
|
|
574
|
+
const resolveSourcePatterns = (patterns, env, srcDir) => {
|
|
575
|
+
const expanded = expandArray(patterns, env, "sources");
|
|
576
|
+
return expanded.map((value) => resolvePath(srcDir, value));
|
|
577
|
+
};
|
|
578
|
+
const buildDefineFlags = (defines) => Object.entries(defines).map(([key, value]) => `-D${key}=${String(value)}`);
|
|
579
|
+
const buildExportFlags = (exports$1) => {
|
|
580
|
+
if (exports$1.length === 0) {
|
|
581
|
+
return [];
|
|
582
|
+
}
|
|
583
|
+
return ["-s", `EXPORTED_FUNCTIONS=${JSON.stringify(exports$1)}`];
|
|
584
|
+
};
|
|
585
|
+
const createEnvForBuild = (baseEnv, overrides) => ({
|
|
586
|
+
...process.env,
|
|
587
|
+
...baseEnv,
|
|
588
|
+
...overrides
|
|
589
|
+
});
|
|
590
|
+
const resolveTargetOutFile = (targetName, targetOutFile, env, outDir) => {
|
|
591
|
+
if (targetOutFile) {
|
|
592
|
+
return resolveOutFile(targetOutFile, env, outDir);
|
|
593
|
+
}
|
|
594
|
+
return path.resolve(outDir, `${targetName}.wasm`);
|
|
595
|
+
};
|
|
596
|
+
const resolveTargetSources = async (targetSources, env, srcDir) => {
|
|
597
|
+
const patterns = targetSources && targetSources.length > 0 ? targetSources : [path.join(srcDir, "**", "*.c"), path.join(srcDir, "**", "*.cpp")];
|
|
598
|
+
const resolvedPatterns = resolveSourcePatterns(patterns, env, srcDir);
|
|
599
|
+
const results = await Promise.all(
|
|
600
|
+
resolvedPatterns.map((pattern) => glob.glob(pattern, { nodir: true }))
|
|
601
|
+
);
|
|
602
|
+
const sources = results.flat();
|
|
603
|
+
sources.sort();
|
|
604
|
+
return sources;
|
|
605
|
+
};
|
|
606
|
+
const toSafeObjectName = (rootDir, sourcePath) => path.relative(rootDir, sourcePath).replace(/[\\/]/g, "_").replace(/[^A-Za-z0-9._-]/g, "_");
|
|
607
|
+
const buildWasm = async (options) => {
|
|
608
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
609
|
+
if (!options) {
|
|
610
|
+
throw new TypeError("options must be provided.");
|
|
611
|
+
}
|
|
612
|
+
if (!options.rule || !options.rule.targets) {
|
|
613
|
+
throw new TypeError("rule targets must be provided.");
|
|
614
|
+
}
|
|
615
|
+
const targets = Object.entries(options.rule.targets);
|
|
616
|
+
if (targets.length === 0) {
|
|
617
|
+
throw new TypeError("rule targets must not be empty.");
|
|
618
|
+
}
|
|
619
|
+
const logger = (_a = options.logger) != null ? _a : createConsoleLogger("emsdk-env");
|
|
620
|
+
const rootDir = path.resolve((_b = options.root) != null ? _b : process.cwd());
|
|
621
|
+
const emsdkOptions = normalizePrepareOptions(options.emsdk);
|
|
622
|
+
const emsdkRoot = await prepareEmsdk(emsdkOptions);
|
|
623
|
+
const emsdkEnv = await loadEmsdkEnv(emsdkRoot, logger, emsdkOptions.signal);
|
|
624
|
+
const baseEnv = {
|
|
625
|
+
...emsdkEnv,
|
|
626
|
+
ROOT: rootDir
|
|
627
|
+
};
|
|
628
|
+
const rawSrcDir = expandPlaceholders(
|
|
629
|
+
(_c = options.srcDir) != null ? _c : DEFAULT_WASM_SRC_DIR,
|
|
630
|
+
baseEnv,
|
|
631
|
+
"srcDir"
|
|
632
|
+
);
|
|
633
|
+
const rawOutDir = expandPlaceholders(
|
|
634
|
+
(_d = options.outDir) != null ? _d : DEFAULT_WASM_OUT_DIR,
|
|
635
|
+
baseEnv,
|
|
636
|
+
"outDir"
|
|
637
|
+
);
|
|
638
|
+
const rawBuildDir = expandPlaceholders(
|
|
639
|
+
(_e = options.buildDir) != null ? _e : DEFAULT_WASM_BUILD_DIR,
|
|
640
|
+
baseEnv,
|
|
641
|
+
"buildDir"
|
|
642
|
+
);
|
|
643
|
+
const srcDir = resolvePath(rootDir, rawSrcDir);
|
|
644
|
+
const outDir = resolvePath(rootDir, rawOutDir);
|
|
645
|
+
const buildDir = resolvePath(rootDir, rawBuildDir);
|
|
646
|
+
const buildId = createBuildId();
|
|
647
|
+
const buildRunDir = path.resolve(buildDir, buildId);
|
|
648
|
+
const cleanupBuildDir = (_f = options.cleanupBuildDir) != null ? _f : true;
|
|
649
|
+
const parallel = (_g = options.parallel) != null ? _g : true;
|
|
650
|
+
const envWithDirs = {
|
|
651
|
+
...emsdkEnv,
|
|
652
|
+
ROOT: rootDir,
|
|
653
|
+
SRC_DIR: srcDir,
|
|
654
|
+
OUT_DIR: outDir,
|
|
655
|
+
BUILD_DIR: buildDir
|
|
656
|
+
};
|
|
657
|
+
const emccCommand = await resolveEmccCommand(envWithDirs, emsdkRoot);
|
|
658
|
+
const common = (_h = options.rule.common) != null ? _h : {};
|
|
659
|
+
await ensureDirectory(outDir);
|
|
660
|
+
await ensureDirectory(buildDir);
|
|
661
|
+
await promises.rm(buildRunDir, { recursive: true, force: true });
|
|
662
|
+
await ensureDirectory(buildRunDir);
|
|
663
|
+
const outFiles = {};
|
|
664
|
+
try {
|
|
665
|
+
for (const [targetName, target] of targets) {
|
|
666
|
+
const mergedOptions = [
|
|
667
|
+
...ensureArray(common.options),
|
|
668
|
+
...ensureArray(target.options)
|
|
669
|
+
];
|
|
670
|
+
const mergedLinkOptions = [
|
|
671
|
+
...ensureArray(common.linkOptions),
|
|
672
|
+
...ensureArray(target.linkOptions)
|
|
673
|
+
];
|
|
674
|
+
const mergedExports = [
|
|
675
|
+
...ensureArray(common.exports),
|
|
676
|
+
...ensureArray(target.exports)
|
|
677
|
+
];
|
|
678
|
+
const mergedIncludeDirs = [
|
|
679
|
+
...ensureArray(common.includeDirs),
|
|
680
|
+
...ensureArray(target.includeDirs)
|
|
681
|
+
];
|
|
682
|
+
const mergedDefines = mergeDefines(common.defines, target.defines);
|
|
683
|
+
const targetEnv = {
|
|
684
|
+
...envWithDirs,
|
|
685
|
+
TARGET_NAME: targetName
|
|
686
|
+
};
|
|
687
|
+
const buildEnv = createEnvForBuild(targetEnv, {});
|
|
688
|
+
const resolvedOutFile = resolveTargetOutFile(
|
|
689
|
+
targetName,
|
|
690
|
+
target.outFile,
|
|
691
|
+
targetEnv,
|
|
692
|
+
outDir
|
|
693
|
+
);
|
|
694
|
+
const sources = await resolveTargetSources(
|
|
695
|
+
target.sources,
|
|
696
|
+
targetEnv,
|
|
697
|
+
srcDir
|
|
698
|
+
);
|
|
699
|
+
if (sources.length === 0) {
|
|
700
|
+
throw new Error(`No sources matched for target: ${targetName}`);
|
|
701
|
+
}
|
|
702
|
+
const targetBuildDir = path.resolve(buildRunDir, targetName);
|
|
703
|
+
await promises.rm(targetBuildDir, { recursive: true, force: true });
|
|
704
|
+
await ensureDirectory(targetBuildDir);
|
|
705
|
+
const resolvedOptions = expandArray(mergedOptions, targetEnv, "options");
|
|
706
|
+
const resolvedLinkOptions = expandArray(
|
|
707
|
+
mergedLinkOptions,
|
|
708
|
+
targetEnv,
|
|
709
|
+
"linkOptions"
|
|
710
|
+
);
|
|
711
|
+
const resolvedExports = expandArray(mergedExports, targetEnv, "exports");
|
|
712
|
+
const exportArgs = buildExportFlags(resolvedExports);
|
|
713
|
+
const includeArgs = resolveIncludeDirs(
|
|
714
|
+
mergedIncludeDirs,
|
|
715
|
+
targetEnv,
|
|
716
|
+
rootDir
|
|
717
|
+
).map((dir) => `-I${dir}`);
|
|
718
|
+
const defineArgs = buildDefineFlags(
|
|
719
|
+
resolveDefines(mergedDefines, targetEnv)
|
|
720
|
+
);
|
|
721
|
+
logger.info(`Compiling target: ${targetName}`);
|
|
722
|
+
const compileSource = async (source) => {
|
|
723
|
+
const objectName = toSafeObjectName(rootDir, source);
|
|
724
|
+
const outputObject = path.resolve(targetBuildDir, `${objectName}.o`);
|
|
725
|
+
const args = [
|
|
726
|
+
"-c",
|
|
727
|
+
source,
|
|
728
|
+
"-o",
|
|
729
|
+
outputObject,
|
|
730
|
+
...resolvedOptions,
|
|
731
|
+
...includeArgs,
|
|
732
|
+
...defineArgs
|
|
733
|
+
];
|
|
734
|
+
logger.debug(`emcc ${args.join(" ")}`);
|
|
735
|
+
await runCommandWithEnv(
|
|
736
|
+
emccCommand,
|
|
737
|
+
args,
|
|
738
|
+
rootDir,
|
|
739
|
+
buildEnv,
|
|
740
|
+
emsdkOptions.signal
|
|
741
|
+
);
|
|
742
|
+
return outputObject;
|
|
743
|
+
};
|
|
744
|
+
const buildObjectsSequential = async () => {
|
|
745
|
+
const objectFiles2 = [];
|
|
746
|
+
for (const source of sources) {
|
|
747
|
+
objectFiles2.push(await compileSource(source));
|
|
748
|
+
}
|
|
749
|
+
return objectFiles2;
|
|
750
|
+
};
|
|
751
|
+
const objectFiles = parallel ? await Promise.all(sources.map((source) => compileSource(source))) : await buildObjectsSequential();
|
|
752
|
+
logger.info(`Linking target: ${targetName}`);
|
|
753
|
+
const linkArgs = [
|
|
754
|
+
...objectFiles,
|
|
755
|
+
"-o",
|
|
756
|
+
resolvedOutFile,
|
|
757
|
+
...resolvedLinkOptions,
|
|
758
|
+
...exportArgs
|
|
759
|
+
];
|
|
760
|
+
logger.debug(`emcc ${linkArgs.join(" ")}`);
|
|
761
|
+
await runCommandWithEnv(
|
|
762
|
+
emccCommand,
|
|
763
|
+
linkArgs,
|
|
764
|
+
rootDir,
|
|
765
|
+
buildEnv,
|
|
766
|
+
emsdkOptions.signal
|
|
767
|
+
);
|
|
768
|
+
outFiles[targetName] = resolvedOutFile;
|
|
769
|
+
}
|
|
770
|
+
} finally {
|
|
771
|
+
if (cleanupBuildDir) {
|
|
772
|
+
await promises.rm(buildRunDir, { recursive: true, force: true });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
emsdkRoot,
|
|
777
|
+
outFiles
|
|
778
|
+
};
|
|
779
|
+
};
|
|
780
|
+
exports.buildWasm = buildWasm;
|
|
781
|
+
exports.prepareEmsdk = prepareEmsdk;
|
|
782
|
+
//# sourceMappingURL=build-oenTJc-4.cjs.map
|