@napi-rs/cli 3.10.4 → 3.10.5
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/dist/cli.js +1174 -53
- package/dist/index.cjs +1173 -52
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +1174 -53
- package/docs/build.md +1 -0
- package/docs/wasi.md +149 -8
- package/package.json +2 -2
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +2498 -132
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/templates.spec.ts +266 -6
- package/src/api/build.ts +38 -1
- package/src/api/templates/load-wasi-template.ts +807 -24
- package/src/commands/__tests__/build.spec.ts +6 -0
- package/src/def/build.ts +13 -0
- package/src/utils/__tests__/ohos-selfsign.spec.ts +285 -0
- package/src/utils/__tests__/reconciliation.spec.ts +19 -0
- package/src/utils/config.ts +10 -6
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +5 -4
- package/src/utils/ohos-selfsign.ts +646 -0
|
Binary file
|
|
@@ -1123,6 +1123,241 @@ for (const { name, code } of eagerWasiLoaderCases) {
|
|
|
1123
1123
|
})
|
|
1124
1124
|
}
|
|
1125
1125
|
|
|
1126
|
+
// `napi_prepare_wasm_env_cleanup` waits: it returns only once the addon's async
|
|
1127
|
+
// runtime has quiesced, and on a threaded artifact the work it waits for can be
|
|
1128
|
+
// waiting for a JavaScript turn from the very thread the export runs on — which
|
|
1129
|
+
// never comes, because that thread is inside the export. The addon exposes the
|
|
1130
|
+
// same teardown as `…_begin` / `napi_wasm_runtime_work_pending` / `…_finish` so
|
|
1131
|
+
// a loader can put real turns in the middle. Every shape that can yield has to
|
|
1132
|
+
// use it, and every shape has to keep working against an addon that has no such
|
|
1133
|
+
// exports.
|
|
1134
|
+
const TWO_PHASE_BARRIER_BY_FLAVOR = {
|
|
1135
|
+
eager: {
|
|
1136
|
+
detect:
|
|
1137
|
+
/if \(typeof begin !== 'function' \|\|\s*typeof finish !== 'function'\) \{/,
|
|
1138
|
+
poll: '}, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)',
|
|
1139
|
+
finish: '.then(finishCleanup, finishCleanup)',
|
|
1140
|
+
fallback: '__prepareWasmEnvCleanup()',
|
|
1141
|
+
report: '__reportUnreachedWasmEnvSettlements()',
|
|
1142
|
+
// The closer a caller that cannot yield uses to end a parked handshake,
|
|
1143
|
+
// and the point at which the poll publishes it.
|
|
1144
|
+
parked: '__finishParkedWasmEnvCleanup',
|
|
1145
|
+
publishParked: '__finishParkedWasmEnvCleanup = finishCleanup',
|
|
1146
|
+
clearParked: '__finishParkedWasmEnvCleanup = undefined',
|
|
1147
|
+
},
|
|
1148
|
+
deferred: {
|
|
1149
|
+
detect:
|
|
1150
|
+
/if \(typeof __begin !== 'function' \|\|\s*typeof __finish !== 'function'\) \{/,
|
|
1151
|
+
poll: '}, __WASM_RUNTIME_WORK_POLL_INTERVAL_MS)',
|
|
1152
|
+
finish: `return __pollWasmRuntimeWork(__workPending).then(
|
|
1153
|
+
__finishEnvCleanup,
|
|
1154
|
+
__finishEnvCleanup,
|
|
1155
|
+
)`,
|
|
1156
|
+
fallback: '__prepareEnvCleanup()',
|
|
1157
|
+
report: '__reportUnreachedSettlements()',
|
|
1158
|
+
parked: '__finishParkedEnvCleanup',
|
|
1159
|
+
publishParked: '__finishParkedEnvCleanup = __finishEnvCleanup',
|
|
1160
|
+
clearParked: '__finishParkedEnvCleanup = undefined',
|
|
1161
|
+
},
|
|
1162
|
+
} as const
|
|
1163
|
+
|
|
1164
|
+
for (const { name, code } of wasiLoaderCases) {
|
|
1165
|
+
const barrier =
|
|
1166
|
+
TWO_PHASE_BARRIER_BY_FLAVOR[
|
|
1167
|
+
code.includes(EAGER_ROLLBACK_SIGNATURE) ? 'eager' : 'deferred'
|
|
1168
|
+
]
|
|
1169
|
+
test(`WASI loader polls the two-phase wasm env cleanup: ${name}`, (t) => {
|
|
1170
|
+
t.true(
|
|
1171
|
+
code.includes('napi_prepare_wasm_env_cleanup_begin'),
|
|
1172
|
+
'loader must start the teardown without joining',
|
|
1173
|
+
)
|
|
1174
|
+
t.true(
|
|
1175
|
+
code.includes('napi_wasm_runtime_work_pending'),
|
|
1176
|
+
'loader must poll the runtime between the two halves; without it there is nothing to wait on',
|
|
1177
|
+
)
|
|
1178
|
+
t.true(
|
|
1179
|
+
code.includes('napi_prepare_wasm_env_cleanup_finish'),
|
|
1180
|
+
'loader must still join: finish is the call that owes quiescence',
|
|
1181
|
+
)
|
|
1182
|
+
// Optional, exactly like every other export in this teardown: an addon
|
|
1183
|
+
// built against a napi crate that predates the split keeps the single
|
|
1184
|
+
// blocking call.
|
|
1185
|
+
t.regex(code, barrier.detect, 'the split must be feature-detected')
|
|
1186
|
+
t.true(
|
|
1187
|
+
code.includes(barrier.fallback),
|
|
1188
|
+
'a loader that finds no split must fall back to the single call',
|
|
1189
|
+
)
|
|
1190
|
+
// Unbounded, and deliberately so. The only way to end the poll early is to
|
|
1191
|
+
// call `…_finish`, which joins on the JavaScript thread — the very thread
|
|
1192
|
+
// the work it joins may be waiting for a turn from — so a budget would not
|
|
1193
|
+
// end that wait, it would only move it somewhere the thread can no longer
|
|
1194
|
+
// be reached. Same reason the async-work drain has no deadline. A host that
|
|
1195
|
+
// breaks the "no blocking closure may wait on a JavaScript turn" rule keeps
|
|
1196
|
+
// its disposal promise pending instead of wedging the thread.
|
|
1197
|
+
t.false(
|
|
1198
|
+
code.includes('__WASM_RUNTIME_DRAIN_TURNS'),
|
|
1199
|
+
'the poll must not carry a turn budget',
|
|
1200
|
+
)
|
|
1201
|
+
t.is(
|
|
1202
|
+
code.split('for (;;) {').length - 1,
|
|
1203
|
+
1,
|
|
1204
|
+
'the poll must be an unbounded loop',
|
|
1205
|
+
)
|
|
1206
|
+
t.true(
|
|
1207
|
+
code.includes(barrier.poll),
|
|
1208
|
+
'the poll must yield with a real referenced timer, not a macrotask spin',
|
|
1209
|
+
)
|
|
1210
|
+
// `__scheduleTimer` falls back to the macrotask scheduler when `setTimeout`
|
|
1211
|
+
// is missing or throws — but not when it is present, returns a handle and
|
|
1212
|
+
// never fires (fake timers; a host whose timers belong to an IO context
|
|
1213
|
+
// that is gone). With no budget left to bail the poll out, that host would
|
|
1214
|
+
// park it forever. Arm both until timers have actually arrived, then pace
|
|
1215
|
+
// on the timer alone rather than spinning the zero-delay queue.
|
|
1216
|
+
//
|
|
1217
|
+
// Whether they arrive is a property of the poll, never of the module: a
|
|
1218
|
+
// host can lose its timers between two disposals, and in the deferred
|
|
1219
|
+
// shape every instance shares this module — one healthy instance must not
|
|
1220
|
+
// disarm the fallback for the next one.
|
|
1221
|
+
t.false(
|
|
1222
|
+
code.includes('let __wasmRuntimePollTimerArrived'),
|
|
1223
|
+
'the pacing state must not outlive the poll that learned it',
|
|
1224
|
+
)
|
|
1225
|
+
t.is(
|
|
1226
|
+
code.split('function __createWasmRuntimePollPace()').length - 1,
|
|
1227
|
+
1,
|
|
1228
|
+
'one definition of the pacing state',
|
|
1229
|
+
)
|
|
1230
|
+
t.is(
|
|
1231
|
+
code.split('__createWasmRuntimePollPace()').length - 1,
|
|
1232
|
+
2,
|
|
1233
|
+
'and exactly one caller: the poll loop, which owns it for its own run',
|
|
1234
|
+
)
|
|
1235
|
+
const yieldStart = code.indexOf('function __yieldWasmRuntimePollTurn(')
|
|
1236
|
+
t.true(yieldStart > 0, 'the poll must yield through one shared turn helper')
|
|
1237
|
+
const yieldTurn = code.slice(yieldStart, code.indexOf('\n}\n', yieldStart))
|
|
1238
|
+
t.true(
|
|
1239
|
+
yieldTurn.includes('__scheduleTimer(') &&
|
|
1240
|
+
yieldTurn.includes('__scheduleMacrotask('),
|
|
1241
|
+
'an undecided turn must arm both primitives, so an inert setTimeout cannot park the poll',
|
|
1242
|
+
)
|
|
1243
|
+
t.true(
|
|
1244
|
+
yieldTurn.includes('pace.arrivals++'),
|
|
1245
|
+
'the timer callback must count that timers work on this host',
|
|
1246
|
+
)
|
|
1247
|
+
t.true(
|
|
1248
|
+
yieldTurn.includes(
|
|
1249
|
+
'pace.arrivals < __WASM_RUNTIME_WORK_POLL_TRUSTED_ARRIVALS',
|
|
1250
|
+
),
|
|
1251
|
+
'and one arrival must not be enough: it can be a timer armed before the host stopped running them',
|
|
1252
|
+
)
|
|
1253
|
+
// A poll that has settled onto the timer alone has nothing left to fall
|
|
1254
|
+
// back on if the timers stop mid-poll — the turn that armed the dead timer
|
|
1255
|
+
// is the turn that parks, and a parked poll schedules nothing that could
|
|
1256
|
+
// notice. Every turn keeps a longer timer outstanding for that.
|
|
1257
|
+
t.true(
|
|
1258
|
+
yieldTurn.includes('__armWasmRuntimePollStallBackup('),
|
|
1259
|
+
'every turn must keep a stall backup outstanding, armed while the timers still work',
|
|
1260
|
+
)
|
|
1261
|
+
const backupStart = code.indexOf(
|
|
1262
|
+
'function __armWasmRuntimePollStallBackup(',
|
|
1263
|
+
)
|
|
1264
|
+
t.true(backupStart > 0, 'the backup must be one shared helper')
|
|
1265
|
+
const stallBackup = code.slice(
|
|
1266
|
+
backupStart,
|
|
1267
|
+
code.indexOf('\n}\n', backupStart),
|
|
1268
|
+
)
|
|
1269
|
+
t.true(
|
|
1270
|
+
stallBackup.includes('pace.arrivals = 0'),
|
|
1271
|
+
'a turn the backup has to end proves the timers stopped: the poll goes back to arming both',
|
|
1272
|
+
)
|
|
1273
|
+
t.true(
|
|
1274
|
+
stallBackup.includes('pace.settleTurn'),
|
|
1275
|
+
'and the backup must end whichever turn is parked, not the one that armed it',
|
|
1276
|
+
)
|
|
1277
|
+
// By due time, never by how long the parked turn has been waiting. A host
|
|
1278
|
+
// runs its timers in due order, so a backup that runs while a turn due a
|
|
1279
|
+
// window earlier is still parked proves that turn's timer was dropped —
|
|
1280
|
+
// and a healthy turn, whose timer runs first and clears `settleTurn`, is
|
|
1281
|
+
// never touched. Measuring the wait instead has a phase hole: arms spaced
|
|
1282
|
+
// further apart than the window leave the turn that parks between them
|
|
1283
|
+
// with no backup young enough to rescue it.
|
|
1284
|
+
t.regex(
|
|
1285
|
+
stallBackup,
|
|
1286
|
+
/pace\.turnTimerDueAt > (?:__)?dueAt - __WASM_RUNTIME_WORK_POLL_STALL_MS/,
|
|
1287
|
+
'the backup must judge by due time, so there is no phase to fall through',
|
|
1288
|
+
)
|
|
1289
|
+
t.false(
|
|
1290
|
+
stallBackup.includes('Date.now() -'),
|
|
1291
|
+
'and it must not measure how long the parked turn has waited',
|
|
1292
|
+
)
|
|
1293
|
+
t.true(
|
|
1294
|
+
yieldTurn.includes('pace.settleTurn = undefined'),
|
|
1295
|
+
'a turn that ends must stop being the parked one, or a later backup reads a due time already answered',
|
|
1296
|
+
)
|
|
1297
|
+
t.regex(
|
|
1298
|
+
yieldTurn,
|
|
1299
|
+
/if \(__settled\??\) \{\s*return\s*\}|if \(settled\) \{\s*return\s*\}/,
|
|
1300
|
+
'whichever primitive loses the race must resolve nothing: one poll per turn',
|
|
1301
|
+
)
|
|
1302
|
+
t.true(
|
|
1303
|
+
code.includes(barrier.finish),
|
|
1304
|
+
'finish must run whether the poll ended, timed out or could not run at all',
|
|
1305
|
+
)
|
|
1306
|
+
// The one caller that cannot yield is the raw `Context.destroy()` the
|
|
1307
|
+
// wrapper intercepts, and the queue it leaves behind is discarded by the
|
|
1308
|
+
// destroy that follows. Say so, once, without throwing.
|
|
1309
|
+
t.true(
|
|
1310
|
+
code.includes(barrier.report),
|
|
1311
|
+
'the single-call barrier must report settlements nothing can reach any more',
|
|
1312
|
+
)
|
|
1313
|
+
// The window between the halves spans real event-loop turns, so a caller
|
|
1314
|
+
// that cannot yield — the CJS 'exit' teardown, the managed beforeExit one —
|
|
1315
|
+
// can land in the middle of one. It cannot wait for the poll; it has to
|
|
1316
|
+
// close the handshake itself, because `…_finish` is the call that joins and
|
|
1317
|
+
// lowers the barrier. Without this the context is destroyed with the
|
|
1318
|
+
// barrier still raised, the runtime never joined and the destroy recorded
|
|
1319
|
+
// as done.
|
|
1320
|
+
t.is(
|
|
1321
|
+
code.split(barrier.publishParked).length - 1,
|
|
1322
|
+
1,
|
|
1323
|
+
'the poll must publish a closer before it yields',
|
|
1324
|
+
)
|
|
1325
|
+
t.is(
|
|
1326
|
+
code.split(barrier.clearParked).length - 1,
|
|
1327
|
+
1,
|
|
1328
|
+
'finishing the handshake must retract that closer exactly once',
|
|
1329
|
+
)
|
|
1330
|
+
const parkedIndex = code.indexOf(`const ${barrier.parked}`)
|
|
1331
|
+
t.true(
|
|
1332
|
+
parkedIndex > 0 || code.includes(`let ${barrier.parked}`),
|
|
1333
|
+
'the closer must live outside the barrier, where a non-yielding caller can reach it',
|
|
1334
|
+
)
|
|
1335
|
+
// And the single call is where it is reached: every teardown path runs the
|
|
1336
|
+
// barrier through it before destroying, so closing a parked handshake there
|
|
1337
|
+
// covers all of them at once.
|
|
1338
|
+
const isEager = barrier.parked === '__finishParkedWasmEnvCleanup'
|
|
1339
|
+
const prepareStart = code.indexOf(
|
|
1340
|
+
isEager
|
|
1341
|
+
? `function ${barrier.fallback.replace('()', '')}() {`
|
|
1342
|
+
: `const ${barrier.fallback.replace('()', '')} = () => {`,
|
|
1343
|
+
)
|
|
1344
|
+
t.true(prepareStart > 0, 'the single-call barrier must be one function')
|
|
1345
|
+
const prepareBody = code.slice(
|
|
1346
|
+
prepareStart,
|
|
1347
|
+
code.indexOf(isEager ? '\n}\n' : '\n }\n', prepareStart),
|
|
1348
|
+
)
|
|
1349
|
+
const closerIndex = prepareBody.indexOf(barrier.parked)
|
|
1350
|
+
t.true(
|
|
1351
|
+
closerIndex > 0,
|
|
1352
|
+
'the single call must close a parked handshake instead of skipping the barrier',
|
|
1353
|
+
)
|
|
1354
|
+
t.true(
|
|
1355
|
+
closerIndex < prepareBody.indexOf('napi_prepare_wasm_env_cleanup'),
|
|
1356
|
+
'and it must close it before looking the single-call export up: a parked handshake is already begun',
|
|
1357
|
+
)
|
|
1358
|
+
})
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1126
1361
|
// The loaders order their own teardown barrier-then-destroy, but the emnapi
|
|
1127
1362
|
// context is a live object: an embedder or test harness holding it, or emnapi's
|
|
1128
1363
|
// own `beforeExit` auto-destroy on a host where `suppressDestroy()` is absent,
|
|
@@ -1140,14 +1375,22 @@ const CONTEXT_DESTROY_WRAP_SIGNATURE =
|
|
|
1140
1375
|
const preparingBarrierGuards = {
|
|
1141
1376
|
shared: {
|
|
1142
1377
|
probe: '__isPreparingWasmEnvCleanup',
|
|
1378
|
+
// Both barrier entry points read it: the single call and the two-phase
|
|
1379
|
+
// form, which keeps it raised across the turns it yields. The single call
|
|
1380
|
+
// splits the check in two, because a handshake parked between the halves is
|
|
1381
|
+
// one it closes rather than refuses — see the two-phase test above — so the
|
|
1382
|
+
// shape below is the reentrancy half alone.
|
|
1383
|
+
entryGuard: ` if (__emnapiWasmEnvCleanupPreparing) {
|
|
1384
|
+
return
|
|
1385
|
+
}`,
|
|
1386
|
+
twoPhaseEntryGuard: ` if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
|
1387
|
+
return
|
|
1388
|
+
}`,
|
|
1143
1389
|
snippets: [
|
|
1144
1390
|
'let __emnapiWasmEnvCleanupPreparing = false',
|
|
1145
1391
|
`function __isPreparingWasmEnvCleanup() {
|
|
1146
1392
|
return __emnapiWasmEnvCleanupPreparing
|
|
1147
1393
|
}`,
|
|
1148
|
-
` if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
|
1149
|
-
return
|
|
1150
|
-
}`,
|
|
1151
1394
|
` __emnapiWasmEnvCleanupPreparing = true
|
|
1152
1395
|
try {
|
|
1153
1396
|
prepare()
|
|
@@ -1158,12 +1401,15 @@ const preparingBarrierGuards = {
|
|
|
1158
1401
|
},
|
|
1159
1402
|
deferred: {
|
|
1160
1403
|
probe: '__isPreparingEnvCleanup',
|
|
1404
|
+
entryGuard: ` if (__wasmEnvCleanupPreparing) {
|
|
1405
|
+
return
|
|
1406
|
+
}`,
|
|
1407
|
+
twoPhaseEntryGuard: ` if (__wasmEnvCleanupPrepared || __wasmEnvCleanupPreparing) {
|
|
1408
|
+
return
|
|
1409
|
+
}`,
|
|
1161
1410
|
snippets: [
|
|
1162
1411
|
'let __wasmEnvCleanupPreparing = false',
|
|
1163
1412
|
'const __isPreparingEnvCleanup = () => __wasmEnvCleanupPreparing',
|
|
1164
|
-
` if (__wasmEnvCleanupPrepared || __wasmEnvCleanupPreparing) {
|
|
1165
|
-
return
|
|
1166
|
-
}`,
|
|
1167
1413
|
` __wasmEnvCleanupPreparing = true
|
|
1168
1414
|
try {
|
|
1169
1415
|
__prepareWasmEnvCleanup()
|
|
@@ -1267,6 +1513,20 @@ for (const { name, code, prepare, guard } of wrappedContextCreationCases) {
|
|
|
1267
1513
|
`barrier must carry its in-flight guard exactly once: ${snippet}`,
|
|
1268
1514
|
)
|
|
1269
1515
|
}
|
|
1516
|
+
// Both barrier entry points — the single call and the two-phase form —
|
|
1517
|
+
// refuse to re-enter a barrier already in flight. The two-phase form still
|
|
1518
|
+
// tests the pair in one condition; the single call tests the reentrancy
|
|
1519
|
+
// half on its own, after it has dealt with a parked handshake.
|
|
1520
|
+
t.is(
|
|
1521
|
+
code.split(guard.entryGuard).length - 1,
|
|
1522
|
+
1,
|
|
1523
|
+
'the single call must refuse to re-enter a barrier already in flight',
|
|
1524
|
+
)
|
|
1525
|
+
t.is(
|
|
1526
|
+
code.split(guard.twoPhaseEntryGuard).length - 1,
|
|
1527
|
+
1,
|
|
1528
|
+
'the two-phase barrier must refuse to re-enter a barrier already in flight',
|
|
1529
|
+
)
|
|
1270
1530
|
t.is(
|
|
1271
1531
|
code.split(NESTED_DESTROY_NO_OP).length - 1,
|
|
1272
1532
|
1,
|
package/src/api/build.ts
CHANGED
|
@@ -42,7 +42,9 @@ import {
|
|
|
42
42
|
removeNodeStreamWebTypeImports,
|
|
43
43
|
rewriteUnboundNodeGlobalTypeQueries,
|
|
44
44
|
rewriteTypeImportReferences,
|
|
45
|
+
checkSelfsign,
|
|
45
46
|
scanExportedName,
|
|
47
|
+
signFileAtomic,
|
|
46
48
|
type Target,
|
|
47
49
|
targetToEnvVar,
|
|
48
50
|
tryInstallCargoBinary,
|
|
@@ -254,7 +256,7 @@ export function checkAsyncRuntimeHostContract({
|
|
|
254
256
|
}
|
|
255
257
|
if (!asyncRuntime && missingHostExports.length === 0) {
|
|
256
258
|
return {
|
|
257
|
-
warning: `${packageName} exports the napi-async-runtime host contract but napi.wasm.asyncRuntime is not enabled. The generated WASI loaders will not install the CurrentThread task and timer hosts, so
|
|
259
|
+
warning: `${packageName} exports the napi-async-runtime host contract but napi.wasm.asyncRuntime is not enabled. The generated WASI loaders will not install the CurrentThread task and timer hosts, so a binding running the CurrentThread flavor (the default on every wasm target) will make no progress unless the hosts are installed by hand. A wasm32-wasip1-threads binding that configures MultiThread does not need them.`,
|
|
258
260
|
}
|
|
259
261
|
}
|
|
260
262
|
return {}
|
|
@@ -1138,6 +1140,7 @@ export async function buildProject(rawOptions: BuildOptions) {
|
|
|
1138
1140
|
|
|
1139
1141
|
const options: ParsedBuildOptions = {
|
|
1140
1142
|
dtsCache: true,
|
|
1143
|
+
ohosSign: true,
|
|
1141
1144
|
...rawOptions,
|
|
1142
1145
|
format: resolveBuildFormat(rawOptions),
|
|
1143
1146
|
cwd: rawOptions.cwd ?? process.cwd(),
|
|
@@ -2466,6 +2469,40 @@ class Builder {
|
|
|
2466
2469
|
} else {
|
|
2467
2470
|
await copyFileAtomic(src, dest)
|
|
2468
2471
|
artifactReplaced = true
|
|
2472
|
+
// OpenHarmony devices refuse to load unsigned `.so` files, so inject
|
|
2473
|
+
// the fs-verity `.codesign` self-signature unless `--no-ohos-sign`.
|
|
2474
|
+
if (
|
|
2475
|
+
this.target.platform === 'openharmony' &&
|
|
2476
|
+
this.options.ohosSign !== false
|
|
2477
|
+
) {
|
|
2478
|
+
debug('Self-signing OpenHarmony artifact:')
|
|
2479
|
+
debug(' %i', dest)
|
|
2480
|
+
try {
|
|
2481
|
+
// Force mode: the OHOS SDK linker can already self-sign artifacts
|
|
2482
|
+
// when its opt-in code-signing is enabled — strip that signature
|
|
2483
|
+
// and re-sign rather than failing on the existing `.codesign`
|
|
2484
|
+
// section.
|
|
2485
|
+
signFileAtomic(dest, true)
|
|
2486
|
+
// Re-verify the signature we just wrote so a corrupt or
|
|
2487
|
+
// mis-injected `.codesign` section is surfaced immediately.
|
|
2488
|
+
const { ok, reason } = checkSelfsign(readFileSync(dest))
|
|
2489
|
+
if (!ok) {
|
|
2490
|
+
debug.warn(
|
|
2491
|
+
`OpenHarmony self-sign verification failed: ${dest}: ${reason}`,
|
|
2492
|
+
)
|
|
2493
|
+
}
|
|
2494
|
+
} catch (e) {
|
|
2495
|
+
// Self-signing is best-effort: HarmonyOS only ships on aarch64,
|
|
2496
|
+
// and the algorithm (like the official binary-sign-tool) is only
|
|
2497
|
+
// exercised against aarch64 artifacts — warn instead of failing
|
|
2498
|
+
// the whole build when e.g. an x86_64 artifact cannot be signed.
|
|
2499
|
+
// signElf rejects before writing anything, so `dest` still holds
|
|
2500
|
+
// the copied artifact.
|
|
2501
|
+
debug.warn(
|
|
2502
|
+
`Skip OpenHarmony self-signing: ${dest}: ${(e as Error).message}`,
|
|
2503
|
+
)
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2469
2506
|
}
|
|
2470
2507
|
this.outputs.push({
|
|
2471
2508
|
kind: dest.endsWith('.node') ? 'node' : isWasm ? 'wasm' : 'exe',
|