@hominis/fireforge 0.52.0 → 0.54.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/CHANGELOG.md +45 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/bootstrap.js +2 -3
- package/dist/src/commands/build-prelock.d.ts +3 -3
- package/dist/src/commands/build-prelock.js +3 -3
- package/dist/src/commands/build.js +4 -0
- package/dist/src/commands/declarations-report.d.ts +9 -3
- package/dist/src/commands/declarations-report.js +18 -11
- package/dist/src/commands/discard.js +3 -3
- package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
- package/dist/src/commands/doctor-furnace.js +5 -5
- package/dist/src/commands/export-adopt-check.d.ts +5 -6
- package/dist/src/commands/export-adopt-check.js +5 -6
- package/dist/src/commands/export-flow.js +6 -9
- package/dist/src/commands/export-staged-registration.d.ts +4 -4
- package/dist/src/commands/export-staged-registration.js +4 -4
- package/dist/src/commands/forward-import-remedy.d.ts +2 -3
- package/dist/src/commands/forward-import-remedy.js +2 -3
- package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
- package/dist/src/commands/furnace/index.js +2 -2
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
- package/dist/src/commands/patch/lint-ignore-from.js +3 -4
- package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
- package/dist/src/commands/patch/move-files-adopt.js +65 -0
- package/dist/src/commands/patch/move-files-create.js +41 -20
- package/dist/src/commands/patch/move-files-into.d.ts +18 -0
- package/dist/src/commands/patch/move-files-into.js +125 -60
- package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
- package/dist/src/commands/patch/move-files-retire.js +91 -0
- package/dist/src/commands/patch/move-files.d.ts +3 -0
- package/dist/src/commands/patch/move-files.js +42 -12
- package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
- package/dist/src/commands/patch/move-size-tier.js +66 -0
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
- package/dist/src/commands/patch/reorder-projection.js +49 -0
- package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
- package/dist/src/commands/patch/reorder-shift.js +307 -0
- package/dist/src/commands/patch/reorder.d.ts +9 -15
- package/dist/src/commands/patch/reorder.js +52 -50
- package/dist/src/commands/patch/split-plan.d.ts +31 -3
- package/dist/src/commands/patch/split-plan.js +36 -12
- package/dist/src/commands/patch/split-staged.d.ts +2 -2
- package/dist/src/commands/patch/split-staged.js +2 -2
- package/dist/src/commands/patch/split.js +18 -5
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
- package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
- package/dist/src/commands/patch/staged-dependency-from.js +15 -9
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +26 -4
- package/dist/src/commands/patch/staged-dependency-remove.js +60 -23
- package/dist/src/commands/patch/staged-dependency.js +35 -24
- package/dist/src/commands/patch/tier.js +2 -2
- package/dist/src/commands/patch-rename-map.d.ts +14 -3
- package/dist/src/commands/patch-rename-map.js +21 -3
- package/dist/src/commands/re-export-adjacent.d.ts +4 -5
- package/dist/src/commands/re-export-lint.d.ts +4 -3
- package/dist/src/commands/re-export-lint.js +4 -3
- package/dist/src/commands/re-export-options.js +8 -2
- package/dist/src/commands/re-export-preflight.js +1 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
- package/dist/src/commands/re-export-purity-scope.js +13 -7
- package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
- package/dist/src/commands/re-export-retired-declarations.js +113 -0
- package/dist/src/commands/re-export-single.js +2 -2
- package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
- package/dist/src/commands/re-export-stale-registrations.js +65 -21
- package/dist/src/commands/re-export.js +4 -0
- package/dist/src/commands/rebase/continue.js +3 -3
- package/dist/src/commands/register.js +5 -4
- package/dist/src/commands/run.js +11 -4
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/test-build-furnace.d.ts +1 -2
- package/dist/src/commands/test-build-furnace.js +1 -2
- package/dist/src/commands/test-build-phase.d.ts +8 -0
- package/dist/src/commands/test-build-phase.js +42 -7
- package/dist/src/commands/test-control-restore.d.ts +40 -0
- package/dist/src/commands/test-control-restore.js +86 -0
- package/dist/src/commands/test-dispatch.js +68 -7
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +6 -0
- package/dist/src/commands/test-orchestrate.js +32 -1
- package/dist/src/commands/test-perf-scope.d.ts +1 -1
- package/dist/src/commands/test-perf-scope.js +1 -1
- package/dist/src/commands/test-prelock.d.ts +18 -8
- package/dist/src/commands/test-prelock.js +42 -14
- package/dist/src/commands/test-register.js +4 -3
- package/dist/src/commands/test-results.d.ts +10 -1
- package/dist/src/commands/test-results.js +20 -3
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +23 -4
- package/dist/src/commands/test-shards.d.ts +7 -3
- package/dist/src/commands/test-shards.js +24 -7
- package/dist/src/commands/test-stale-gate.d.ts +6 -1
- package/dist/src/commands/test-stale-gate.js +40 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +14 -2
- package/dist/src/commands/token.js +2 -2
- package/dist/src/commands/verify.js +1 -1
- package/dist/src/commands/watch.js +1 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/build-audit-resolve.d.ts +4 -5
- package/dist/src/core/build-audit-resolve.js +4 -5
- package/dist/src/core/build-audit-skip.d.ts +2 -2
- package/dist/src/core/build-audit.d.ts +3 -4
- package/dist/src/core/build-baseline-types.d.ts +24 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +127 -35
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +25 -16
- package/dist/src/core/build-jar-escalation.d.ts +4 -4
- package/dist/src/core/build-jar-escalation.js +4 -4
- package/dist/src/core/build-prepare.js +4 -4
- package/dist/src/core/config-validate.js +4 -4
- package/dist/src/core/coverage-extend.d.ts +5 -3
- package/dist/src/core/coverage-extend.js +8 -3
- package/dist/src/core/coverage-records.d.ts +4 -5
- package/dist/src/core/coverage-records.js +4 -5
- package/dist/src/core/destructive.d.ts +2 -2
- package/dist/src/core/destructive.js +2 -2
- package/dist/src/core/discard-baseline.d.ts +2 -2
- package/dist/src/core/discard-baseline.js +3 -3
- package/dist/src/core/engine-session-lock.d.ts +1 -1
- package/dist/src/core/engine-session-lock.js +1 -1
- package/dist/src/core/file-lock-owner.d.ts +5 -5
- package/dist/src/core/file-lock-owner.js +5 -5
- package/dist/src/core/file-lock-queue.js +6 -8
- package/dist/src/core/file-lock-shared.d.ts +4 -4
- package/dist/src/core/file-lock-shared.js +4 -4
- package/dist/src/core/file-lock.js +7 -10
- package/dist/src/core/firefox.js +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
- package/dist/src/core/furnace-apply.js +3 -3
- package/dist/src/core/furnace-operation.js +5 -5
- package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
- package/dist/src/core/furnace-registration-rename.d.ts +5 -5
- package/dist/src/core/furnace-registration-rename.js +5 -5
- package/dist/src/core/furnace-registration-validate.d.ts +2 -1
- package/dist/src/core/furnace-registration-validate.js +2 -1
- package/dist/src/core/furnace-registration.js +2 -2
- package/dist/src/core/git-base.js +4 -3
- package/dist/src/core/git-diff.js +1 -1
- package/dist/src/core/git.js +1 -1
- package/dist/src/core/harness-orphans.d.ts +6 -6
- package/dist/src/core/harness-orphans.js +6 -6
- package/dist/src/core/harness-port-reservation.d.ts +60 -0
- package/dist/src/core/harness-port-reservation.js +122 -0
- package/dist/src/core/history-log-file.d.ts +9 -9
- package/dist/src/core/history-log-file.js +15 -15
- package/dist/src/core/history-log.d.ts +3 -3
- package/dist/src/core/history-log.js +3 -3
- package/dist/src/core/launchable-build.d.ts +39 -0
- package/dist/src/core/launchable-build.js +85 -0
- package/dist/src/core/marionette-port.js +1 -3
- package/dist/src/core/moz-manifest-rules.js +3 -2
- package/dist/src/core/negative-control-compiled.d.ts +13 -3
- package/dist/src/core/negative-control-compiled.js +27 -5
- package/dist/src/core/negative-control-diff.js +31 -6
- package/dist/src/core/parser-fallback.d.ts +5 -5
- package/dist/src/core/parser-fallback.js +5 -5
- package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
- package/dist/src/core/patch-identifier-suggest.js +11 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
- package/dist/src/core/patch-lint-checkjs.js +1 -1
- package/dist/src/core/patch-lint-css.js +2 -2
- package/dist/src/core/patch-lint-file-size.d.ts +5 -7
- package/dist/src/core/patch-lint-file-size.js +5 -7
- package/dist/src/core/patch-lint-ignore.d.ts +3 -3
- package/dist/src/core/patch-lint-ignore.js +3 -3
- package/dist/src/core/patch-lint-prettier.d.ts +4 -6
- package/dist/src/core/patch-lint-prettier.js +4 -6
- package/dist/src/core/patch-lint-projection.d.ts +1 -1
- package/dist/src/core/patch-lint-projection.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
- package/dist/src/core/patch-lint-queue-types.js +2 -2
- package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
- package/dist/src/core/patch-manifest-consistency.js +2 -2
- package/dist/src/core/patch-manifest-io.d.ts +5 -1
- package/dist/src/core/patch-manifest-io.js +12 -10
- package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
- package/dist/src/core/patch-manifest-key-order.js +3 -4
- package/dist/src/core/patch-parse.d.ts +3 -3
- package/dist/src/core/patch-parse.js +3 -3
- package/dist/src/core/patch-policy.d.ts +1 -1
- package/dist/src/core/patch-policy.js +1 -1
- package/dist/src/core/patch-source-metadata.d.ts +1 -1
- package/dist/src/core/patch-source-metadata.js +1 -1
- package/dist/src/core/patch-subset.d.ts +5 -3
- package/dist/src/core/patch-subset.js +8 -7
- package/dist/src/core/patch-transform.d.ts +2 -2
- package/dist/src/core/patch-transform.js +2 -2
- package/dist/src/core/rebase-session.js +4 -6
- package/dist/src/core/register-xpcshell-test.d.ts +28 -7
- package/dist/src/core/register-xpcshell-test.js +76 -27
- package/dist/src/core/run-log.d.ts +8 -8
- package/dist/src/core/run-log.js +8 -8
- package/dist/src/core/status-classify.d.ts +1 -2
- package/dist/src/core/status-classify.js +1 -2
- package/dist/src/core/test-harness-crash.d.ts +5 -3
- package/dist/src/core/test-harness-crash.js +13 -14
- package/dist/src/core/test-harness-infra.d.ts +32 -0
- package/dist/src/core/test-harness-infra.js +69 -0
- package/dist/src/core/test-harness-signature.d.ts +6 -0
- package/dist/src/core/test-stale-check.d.ts +38 -0
- package/dist/src/core/test-stale-check.js +82 -1
- package/dist/src/core/test-stall-triage.js +5 -7
- package/dist/src/core/token-category.js +2 -2
- package/dist/src/core/token-inventory.js +3 -3
- package/dist/src/core/token-manager.d.ts +2 -2
- package/dist/src/core/token-manager.js +5 -7
- package/dist/src/core/token-variant.d.ts +6 -6
- package/dist/src/core/token-variant.js +7 -7
- package/dist/src/core/typecheck-shim.js +1 -1
- package/dist/src/core/wire-destroy.d.ts +1 -1
- package/dist/src/core/wire-destroy.js +1 -1
- package/dist/src/core/wire-dom-fragment.js +1 -1
- package/dist/src/core/wire-init.d.ts +1 -1
- package/dist/src/core/wire-init.js +1 -1
- package/dist/src/core/wire-subscript.d.ts +1 -1
- package/dist/src/core/wire-subscript.js +1 -1
- package/dist/src/core/wire-utils.d.ts +2 -2
- package/dist/src/core/wire-utils.js +3 -3
- package/dist/src/errors/base.d.ts +1 -1
- package/dist/src/errors/base.js +1 -1
- package/dist/src/errors/rebase.d.ts +1 -1
- package/dist/src/errors/rebase.js +1 -1
- package/dist/src/types/commands/options.d.ts +38 -3
- package/dist/src/types/config.d.ts +5 -7
- package/dist/src/utils/options.js +2 -2
- package/dist/src/utils/process.js +2 -2
- package/dist/src/utils/regex.d.ts +2 -2
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/sleep.d.ts +5 -6
- package/dist/src/utils/sleep.js +5 -6
- package/dist/src/utils/validation.js +2 -2
- package/docs/configuration.md +28 -23
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +3 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +41 -24
- package/docs/patch-workflow.md +140 -29
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +114 -48
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mochitest server ports for `fireforge test --jobs <n>`: placeholder
|
|
3
|
+
* listeners that push every parallel harness instance onto its own ports,
|
|
4
|
+
* and the parse of the ports each instance actually took.
|
|
5
|
+
*
|
|
6
|
+
* Desktop `mach mochitest` has no `--http-port`, `--ssl-port` or
|
|
7
|
+
* `--websocket-port`. Its `startServers` (`testing/mochitest/runtests.py`)
|
|
8
|
+
* bind-probes the HTTP server, ssltunnel and WebSocket ports (8888, 4443,
|
|
9
|
+
* 9988) and moves only a busy one to a free port; see
|
|
10
|
+
* `harness-port-fallback.ts`. Instances started at the same moment all
|
|
11
|
+
* pass that probe on a free 8888, then race to bind it: the losers die with
|
|
12
|
+
* `could not start server on port 8888` before any test runs. Holding the
|
|
13
|
+
* three ports for the whole parallel run makes every instance's probe fail,
|
|
14
|
+
* so each one falls back to an OS-assigned port of its own.
|
|
15
|
+
*/
|
|
16
|
+
/** Which mochitest server a port belongs to, as the verdict line names it. */
|
|
17
|
+
export type HarnessServerPortKey = 'http' | 'ssl' | 'ws';
|
|
18
|
+
/** Placeholder listeners held for a parallel run. */
|
|
19
|
+
export interface HarnessPortReservation {
|
|
20
|
+
/** Ports this process now holds. A port that was already busy is not listed. */
|
|
21
|
+
held: number[];
|
|
22
|
+
/** Closes every placeholder. Safe to call more than once. */
|
|
23
|
+
release(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Holds a placeholder listener on `127.0.0.1` on each mochitest server
|
|
27
|
+
* port, so the harness's busy-port probe moves every instance elsewhere.
|
|
28
|
+
* A port that cannot be bound is skipped, not refused: whatever holds it
|
|
29
|
+
* already makes the probe fail, which is all the placeholder is for (the
|
|
30
|
+
* preflight has already ruled out this checkout's own stale httpd).
|
|
31
|
+
*
|
|
32
|
+
* @param ports - Ports to hold (default: {@link HARNESS_SERVER_PORTS})
|
|
33
|
+
*/
|
|
34
|
+
export declare function reserveHarnessServerPorts(ports?: readonly number[]): Promise<HarnessPortReservation>;
|
|
35
|
+
/** The server ports one harness invocation reported, by server. */
|
|
36
|
+
export type HarnessServerPorts = Partial<Record<HarnessServerPortKey, number>>;
|
|
37
|
+
/**
|
|
38
|
+
* Records the port one output line reports, if it reports one: the
|
|
39
|
+
* fallback port of a "falling back" line, or the port itself otherwise.
|
|
40
|
+
*
|
|
41
|
+
* @param line - One line of harness output
|
|
42
|
+
* @param into - Collected ports; a later line for the same server wins
|
|
43
|
+
*/
|
|
44
|
+
export declare function recordHarnessServerPortLine(line: string, into: HarnessServerPorts): void;
|
|
45
|
+
/** Collects server ports from streamed output chunks. */
|
|
46
|
+
export interface HarnessPortTracker {
|
|
47
|
+
onChunk(stream: 'stdout' | 'stderr', chunk: string): void;
|
|
48
|
+
/** The ports seen so far, including an unterminated last line. */
|
|
49
|
+
ports(): HarnessServerPorts;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A tracker over the full output stream. The ports are printed at harness
|
|
53
|
+
* start, so the capped capture tail a long run keeps would lose them.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createHarnessPortTracker(): HarnessPortTracker;
|
|
56
|
+
/**
|
|
57
|
+
* The `http:N,ssl:N,ws:N` value of a `ports=` key, in probe order, or
|
|
58
|
+
* undefined when no port is known.
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatHarnessServerPorts(ports: HarnessServerPorts | undefined): string | undefined;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Mochitest server ports for `fireforge test --jobs <n>`: placeholder
|
|
4
|
+
* listeners that push every parallel harness instance onto its own ports,
|
|
5
|
+
* and the parse of the ports each instance actually took.
|
|
6
|
+
*
|
|
7
|
+
* Desktop `mach mochitest` has no `--http-port`, `--ssl-port` or
|
|
8
|
+
* `--websocket-port`. Its `startServers` (`testing/mochitest/runtests.py`)
|
|
9
|
+
* bind-probes the HTTP server, ssltunnel and WebSocket ports (8888, 4443,
|
|
10
|
+
* 9988) and moves only a busy one to a free port; see
|
|
11
|
+
* `harness-port-fallback.ts`. Instances started at the same moment all
|
|
12
|
+
* pass that probe on a free 8888, then race to bind it: the losers die with
|
|
13
|
+
* `could not start server on port 8888` before any test runs. Holding the
|
|
14
|
+
* three ports for the whole parallel run makes every instance's probe fail,
|
|
15
|
+
* so each one falls back to an OS-assigned port of its own.
|
|
16
|
+
*/
|
|
17
|
+
import { createServer } from 'node:net';
|
|
18
|
+
/**
|
|
19
|
+
* The ports `startServers` probes, in the order it probes them. Desktop
|
|
20
|
+
* mochitest cannot configure them, so the engine defaults are the ports.
|
|
21
|
+
*/
|
|
22
|
+
const HARNESS_SERVER_PORTS = [
|
|
23
|
+
{ key: 'http', port: 8888, label: 'HTTP test server' },
|
|
24
|
+
{ key: 'ssl', port: 4443, label: 'ssltunnel' },
|
|
25
|
+
{ key: 'ws', port: 9988, label: 'WebSocket server' },
|
|
26
|
+
];
|
|
27
|
+
/** Listens on `127.0.0.1:port`, or resolves undefined when the bind fails. */
|
|
28
|
+
async function holdPort(port) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const server = createServer((socket) => {
|
|
31
|
+
// Nothing should connect: the harness only probes with a bind. Anyone
|
|
32
|
+
// who does gets a closed socket instead of a hang.
|
|
33
|
+
socket.destroy();
|
|
34
|
+
});
|
|
35
|
+
server.once('error', () => {
|
|
36
|
+
resolve(undefined);
|
|
37
|
+
});
|
|
38
|
+
server.listen({ port, host: '127.0.0.1', exclusive: true }, () => {
|
|
39
|
+
server.unref();
|
|
40
|
+
resolve(server);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Holds a placeholder listener on `127.0.0.1` on each mochitest server
|
|
46
|
+
* port, so the harness's busy-port probe moves every instance elsewhere.
|
|
47
|
+
* A port that cannot be bound is skipped, not refused: whatever holds it
|
|
48
|
+
* already makes the probe fail, which is all the placeholder is for (the
|
|
49
|
+
* preflight has already ruled out this checkout's own stale httpd).
|
|
50
|
+
*
|
|
51
|
+
* @param ports - Ports to hold (default: {@link HARNESS_SERVER_PORTS})
|
|
52
|
+
*/
|
|
53
|
+
export async function reserveHarnessServerPorts(ports = HARNESS_SERVER_PORTS.map((spec) => spec.port)) {
|
|
54
|
+
const servers = (await Promise.all(ports.map(holdPort))).filter((server) => server !== undefined);
|
|
55
|
+
const held = servers.map((server) => {
|
|
56
|
+
const address = server.address();
|
|
57
|
+
return typeof address === 'object' && address !== null ? address.port : 0;
|
|
58
|
+
});
|
|
59
|
+
let released = false;
|
|
60
|
+
return {
|
|
61
|
+
held,
|
|
62
|
+
async release() {
|
|
63
|
+
if (released)
|
|
64
|
+
return;
|
|
65
|
+
released = true;
|
|
66
|
+
await Promise.all(servers.map((server) => new Promise((resolve) => {
|
|
67
|
+
server.close(() => {
|
|
68
|
+
resolve();
|
|
69
|
+
});
|
|
70
|
+
})));
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const PORT_LINE_PATTERN = /\b(HTTP test server|ssltunnel|WebSocket server) port (\d+)(?: already in use, falling back to port (\d+))?/;
|
|
75
|
+
/**
|
|
76
|
+
* Records the port one output line reports, if it reports one: the
|
|
77
|
+
* fallback port of a "falling back" line, or the port itself otherwise.
|
|
78
|
+
*
|
|
79
|
+
* @param line - One line of harness output
|
|
80
|
+
* @param into - Collected ports; a later line for the same server wins
|
|
81
|
+
*/
|
|
82
|
+
export function recordHarnessServerPortLine(line, into) {
|
|
83
|
+
const match = PORT_LINE_PATTERN.exec(line);
|
|
84
|
+
if (match === null)
|
|
85
|
+
return;
|
|
86
|
+
const spec = HARNESS_SERVER_PORTS.find((entry) => entry.label === match[1]);
|
|
87
|
+
const port = Number.parseInt(match[3] ?? match[2] ?? '', 10);
|
|
88
|
+
if (spec !== undefined && Number.isInteger(port))
|
|
89
|
+
into[spec.key] = port;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A tracker over the full output stream. The ports are printed at harness
|
|
93
|
+
* start, so the capped capture tail a long run keeps would lose them.
|
|
94
|
+
*/
|
|
95
|
+
export function createHarnessPortTracker() {
|
|
96
|
+
const collected = {};
|
|
97
|
+
const pending = { stdout: '', stderr: '' };
|
|
98
|
+
return {
|
|
99
|
+
onChunk(stream, chunk) {
|
|
100
|
+
const lines = `${pending[stream]}${chunk}`.split(/\r?\n/);
|
|
101
|
+
pending[stream] = lines.pop() ?? '';
|
|
102
|
+
for (const line of lines)
|
|
103
|
+
recordHarnessServerPortLine(line, collected);
|
|
104
|
+
},
|
|
105
|
+
ports() {
|
|
106
|
+
const result = { ...collected };
|
|
107
|
+
recordHarnessServerPortLine(pending.stdout, result);
|
|
108
|
+
recordHarnessServerPortLine(pending.stderr, result);
|
|
109
|
+
return result;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The `http:N,ssl:N,ws:N` value of a `ports=` key, in probe order, or
|
|
115
|
+
* undefined when no port is known.
|
|
116
|
+
*/
|
|
117
|
+
export function formatHarnessServerPorts(ports) {
|
|
118
|
+
if (ports === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
const parts = HARNESS_SERVER_PORTS.flatMap(({ key }) => ports[key] === undefined ? [] : [`${key}:${ports[key]}`]);
|
|
121
|
+
return parts.length > 0 ? parts.join(',') : undefined;
|
|
122
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Filename of
|
|
1
|
+
/** Filename of a history log inside `patches/`, which is moved out of the queue. */
|
|
2
2
|
export declare const LEGACY_HISTORY_LOG_FILENAME = ".fireforge-history.jsonl";
|
|
3
3
|
/** Default history log location, relative to the project root. */
|
|
4
4
|
export declare const DEFAULT_HISTORY_LOG_PATH: string;
|
|
@@ -29,30 +29,30 @@ export declare function historyRecordIdentity(): HistoryRecordIdentity;
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveHistoryLogPath(projectRoot: string): Promise<string>;
|
|
31
31
|
/**
|
|
32
|
-
* Moves a history log
|
|
32
|
+
* Moves a history log found at the `patches/` path to `target`.
|
|
33
33
|
*
|
|
34
34
|
* Records are never dropped, whatever else is happening:
|
|
35
|
-
* - The
|
|
35
|
+
* - The in-queue file is first claimed by renaming it aside, so of two
|
|
36
36
|
* commands migrating at once exactly one does the work and neither
|
|
37
37
|
* duplicates it.
|
|
38
38
|
* - When `target` does not exist yet (the first run), the claimed file is
|
|
39
39
|
* hard-linked into place, which cannot overwrite a log another command
|
|
40
40
|
* created in the meantime, and keeps a large log a constant-time move.
|
|
41
|
-
* - When both exist (
|
|
42
|
-
*
|
|
41
|
+
* - When both exist (a log reappeared at the `patches/` path after a
|
|
42
|
+
* migration, or `history.path` changed), the in-queue records are
|
|
43
43
|
* appended to the target in one write. They then follow newer records;
|
|
44
44
|
* every record carries its own `ts`.
|
|
45
45
|
*
|
|
46
46
|
* A crash between the claim and the move leaves the records in a
|
|
47
|
-
* `.migrating-*` file next to the
|
|
47
|
+
* `.migrating-*` file next to the in-queue path, never deleted.
|
|
48
48
|
*
|
|
49
|
-
* @param patchesDir - The patches directory that may hold
|
|
49
|
+
* @param patchesDir - The patches directory that may hold an in-queue log
|
|
50
50
|
* @param target - Absolute path of the history log
|
|
51
51
|
*/
|
|
52
52
|
export declare function migrateLegacyHistoryLog(patchesDir: string, target: string): Promise<void>;
|
|
53
53
|
/**
|
|
54
|
-
* Appends one serialized record to the project's history log,
|
|
55
|
-
*
|
|
54
|
+
* Appends one serialized record to the project's history log, first moving
|
|
55
|
+
* a log found inside `patches/`.
|
|
56
56
|
*
|
|
57
57
|
* @param patchesDir - The project's `patches/` directory; the project root is
|
|
58
58
|
* its parent, as every caller derives it from `getProjectPaths`
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Where the patch history log lives, and who wrote each record.
|
|
4
4
|
*
|
|
5
|
-
* The log
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `fireforge
|
|
10
|
-
*
|
|
5
|
+
* The log defaults to `.fireforge/history.jsonl` (FireForge's own untracked
|
|
6
|
+
* data directory), or `fireforge.json#history.path` when a project sets one.
|
|
7
|
+
* It stays out of the tracked patch queue because it grows to tens of
|
|
8
|
+
* megabytes and would otherwise be committed with every export. A log found
|
|
9
|
+
* at `patches/.fireforge-history.jsonl`, inside the queue, is moved on the
|
|
10
|
+
* first append.
|
|
11
11
|
*
|
|
12
12
|
* Several sessions sharing one checkout all append to the same log, so each
|
|
13
13
|
* record carries the invocation that wrote it, plus a session hint, letting
|
|
@@ -21,7 +21,7 @@ import { ensureDir, pathExists } from '../utils/fs.js';
|
|
|
21
21
|
import { info, verbose } from '../utils/logger.js';
|
|
22
22
|
import { FIREFORGE_DIR } from './config-paths.js';
|
|
23
23
|
import { isValidHistoryPath } from './config-validate-history.js';
|
|
24
|
-
/** Filename of
|
|
24
|
+
/** Filename of a history log inside `patches/`, which is moved out of the queue. */
|
|
25
25
|
export const LEGACY_HISTORY_LOG_FILENAME = '.fireforge-history.jsonl';
|
|
26
26
|
/** Default history log location, relative to the project root. */
|
|
27
27
|
export const DEFAULT_HISTORY_LOG_PATH = join(FIREFORGE_DIR, 'history.jsonl');
|
|
@@ -87,24 +87,24 @@ export async function resolveHistoryLogPath(projectRoot) {
|
|
|
87
87
|
return resolve(projectRoot, configured ?? DEFAULT_HISTORY_LOG_PATH);
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Moves a history log
|
|
90
|
+
* Moves a history log found at the `patches/` path to `target`.
|
|
91
91
|
*
|
|
92
92
|
* Records are never dropped, whatever else is happening:
|
|
93
|
-
* - The
|
|
93
|
+
* - The in-queue file is first claimed by renaming it aside, so of two
|
|
94
94
|
* commands migrating at once exactly one does the work and neither
|
|
95
95
|
* duplicates it.
|
|
96
96
|
* - When `target` does not exist yet (the first run), the claimed file is
|
|
97
97
|
* hard-linked into place, which cannot overwrite a log another command
|
|
98
98
|
* created in the meantime, and keeps a large log a constant-time move.
|
|
99
|
-
* - When both exist (
|
|
100
|
-
*
|
|
99
|
+
* - When both exist (a log reappeared at the `patches/` path after a
|
|
100
|
+
* migration, or `history.path` changed), the in-queue records are
|
|
101
101
|
* appended to the target in one write. They then follow newer records;
|
|
102
102
|
* every record carries its own `ts`.
|
|
103
103
|
*
|
|
104
104
|
* A crash between the claim and the move leaves the records in a
|
|
105
|
-
* `.migrating-*` file next to the
|
|
105
|
+
* `.migrating-*` file next to the in-queue path, never deleted.
|
|
106
106
|
*
|
|
107
|
-
* @param patchesDir - The patches directory that may hold
|
|
107
|
+
* @param patchesDir - The patches directory that may hold an in-queue log
|
|
108
108
|
* @param target - Absolute path of the history log
|
|
109
109
|
*/
|
|
110
110
|
export async function migrateLegacyHistoryLog(patchesDir, target) {
|
|
@@ -149,8 +149,8 @@ export async function migrateLegacyHistoryLog(patchesDir, target) {
|
|
|
149
149
|
'Nothing reads it back; it is an audit trail only.');
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
* Appends one serialized record to the project's history log,
|
|
153
|
-
*
|
|
152
|
+
* Appends one serialized record to the project's history log, first moving
|
|
153
|
+
* a log found inside `patches/`.
|
|
154
154
|
*
|
|
155
155
|
* @param patchesDir - The project's `patches/` directory; the project root is
|
|
156
156
|
* its parent, as every caller derives it from `getProjectPaths`
|
|
@@ -3,13 +3,13 @@ import type { HistoryEntry } from './destructive.js';
|
|
|
3
3
|
* Appends a history entry for a mutation that has already committed, degrading
|
|
4
4
|
* to a warning when the log cannot be written.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* This is the one `try { await appendHistory(...) } catch { warn(...) }`
|
|
7
|
+
* block for every call site across `export-flow` and the `patch` subcommands.
|
|
8
8
|
* The swallow is intentional: the patch files and the manifest
|
|
9
9
|
* are already on disk by the time this runs, so letting a failed log write
|
|
10
10
|
* reject would report a completed, irreversible mutation as a failure and send
|
|
11
11
|
* the operator looking for damage that is not there. Because that reasoning is
|
|
12
|
-
* easy to lose in a copy, it
|
|
12
|
+
* easy to lose in a copy, it lives in one place.
|
|
13
13
|
* @param patchesDir - The project's `patches/` directory (the log itself lives
|
|
14
14
|
* under `.fireforge/` or `history.path`; see `history-log-file.ts`).
|
|
15
15
|
* @param entry - The entry to append.
|
|
@@ -6,13 +6,13 @@ import { appendHistory } from './destructive.js';
|
|
|
6
6
|
* Appends a history entry for a mutation that has already committed, degrading
|
|
7
7
|
* to a warning when the log cannot be written.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* This is the one `try { await appendHistory(...) } catch { warn(...) }`
|
|
10
|
+
* block for every call site across `export-flow` and the `patch` subcommands.
|
|
11
11
|
* The swallow is intentional: the patch files and the manifest
|
|
12
12
|
* are already on disk by the time this runs, so letting a failed log write
|
|
13
13
|
* reject would report a completed, irreversible mutation as a failure and send
|
|
14
14
|
* the operator looking for damage that is not there. Because that reasoning is
|
|
15
|
-
* easy to lose in a copy, it
|
|
15
|
+
* easy to lose in a copy, it lives in one place.
|
|
16
16
|
* @param patchesDir - The project's `patches/` directory (the log itself lives
|
|
17
17
|
* under `.fireforge/` or `history.path`; see `history-log-file.ts`).
|
|
18
18
|
* @param entry - The entry to append.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facts about the launchable build a test run will exercise: its build id
|
|
3
|
+
* and when its compiled code was last linked.
|
|
4
|
+
*
|
|
5
|
+
* A green or red against a binary older than the C++ under test reads as a
|
|
6
|
+
* verdict on that C++ unless the run says which binary it was. The
|
|
7
|
+
* build id goes on the verdict line (`build-id=`), and the link time anchors
|
|
8
|
+
* the stale-compiled preflight (`findCompiledSourcesNewerThanBinary` in
|
|
9
|
+
* `test-stale-check.ts`).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The `application.ini` beside a launchable binary: `Contents/Resources`
|
|
13
|
+
* of the `.app` bundle on macOS, the binary's own directory (`dist/bin`)
|
|
14
|
+
* elsewhere.
|
|
15
|
+
*
|
|
16
|
+
* @param binaryPath - Absolute launchable binary path
|
|
17
|
+
*/
|
|
18
|
+
export declare function applicationIniPath(binaryPath: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Reads the `BuildID=` of the launchable build (the `moz:buildID` the
|
|
21
|
+
* browser reports). Undefined when the file or the key is missing, so a
|
|
22
|
+
* caller can simply leave the attribute off.
|
|
23
|
+
*
|
|
24
|
+
* @param engineDir - Absolute engine directory
|
|
25
|
+
* @param launchablePath - Engine-relative launchable binary path
|
|
26
|
+
*/
|
|
27
|
+
export declare function readBuildId(engineDir: string, launchablePath: string): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* When the launchable build's compiled code was last linked: the newest
|
|
30
|
+
* mtime of the binary and the `libxul` beside it. Undefined when neither
|
|
31
|
+
* can be read.
|
|
32
|
+
*
|
|
33
|
+
* @param engineDir - Absolute engine directory
|
|
34
|
+
* @param launchablePath - Engine-relative launchable binary path
|
|
35
|
+
*/
|
|
36
|
+
export declare function readLinkedAt(engineDir: string, launchablePath: string): Promise<{
|
|
37
|
+
mtimeMs: number;
|
|
38
|
+
path: string;
|
|
39
|
+
} | undefined>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Facts about the launchable build a test run will exercise: its build id
|
|
4
|
+
* and when its compiled code was last linked.
|
|
5
|
+
*
|
|
6
|
+
* A green or red against a binary older than the C++ under test reads as a
|
|
7
|
+
* verdict on that C++ unless the run says which binary it was. The
|
|
8
|
+
* build id goes on the verdict line (`build-id=`), and the link time anchors
|
|
9
|
+
* the stale-compiled preflight (`findCompiledSourcesNewerThanBinary` in
|
|
10
|
+
* `test-stale-check.ts`).
|
|
11
|
+
*/
|
|
12
|
+
import { stat } from 'node:fs/promises';
|
|
13
|
+
import { basename, dirname, join } from 'node:path';
|
|
14
|
+
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
15
|
+
import { readText } from '../utils/fs.js';
|
|
16
|
+
import { verbose } from '../utils/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* The compiled libraries a build links next to the launchable binary.
|
|
19
|
+
* `libxul` carries nearly all compiled engine code (C++, Rust, generated
|
|
20
|
+
* bindings, prefs), so it, not the small launcher executable, is what a
|
|
21
|
+
* compiled-source edit relinks.
|
|
22
|
+
*/
|
|
23
|
+
const LINKED_LIBRARY_NAMES = ['XUL', 'libxul.so', 'xul.dll'];
|
|
24
|
+
/**
|
|
25
|
+
* The `application.ini` beside a launchable binary: `Contents/Resources`
|
|
26
|
+
* of the `.app` bundle on macOS, the binary's own directory (`dist/bin`)
|
|
27
|
+
* elsewhere.
|
|
28
|
+
*
|
|
29
|
+
* @param binaryPath - Absolute launchable binary path
|
|
30
|
+
*/
|
|
31
|
+
export function applicationIniPath(binaryPath) {
|
|
32
|
+
const binaryDir = dirname(binaryPath);
|
|
33
|
+
if (basename(binaryDir) === 'MacOS' && basename(dirname(binaryDir)) === 'Contents') {
|
|
34
|
+
return join(dirname(binaryDir), 'Resources', 'application.ini');
|
|
35
|
+
}
|
|
36
|
+
return join(binaryDir, 'application.ini');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reads the `BuildID=` of the launchable build (the `moz:buildID` the
|
|
40
|
+
* browser reports). Undefined when the file or the key is missing, so a
|
|
41
|
+
* caller can simply leave the attribute off.
|
|
42
|
+
*
|
|
43
|
+
* @param engineDir - Absolute engine directory
|
|
44
|
+
* @param launchablePath - Engine-relative launchable binary path
|
|
45
|
+
*/
|
|
46
|
+
export async function readBuildId(engineDir, launchablePath) {
|
|
47
|
+
const iniPath = applicationIniPath(join(engineDir, launchablePath));
|
|
48
|
+
let content;
|
|
49
|
+
try {
|
|
50
|
+
content = await readText(iniPath);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (getNodeErrorCode(error) !== 'ENOENT') {
|
|
54
|
+
verbose(`Build id: could not read ${iniPath} — ${toError(error).message}`);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const match = /^BuildID=(\S+)\s*$/m.exec(content);
|
|
59
|
+
return match?.[1];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* When the launchable build's compiled code was last linked: the newest
|
|
63
|
+
* mtime of the binary and the `libxul` beside it. Undefined when neither
|
|
64
|
+
* can be read.
|
|
65
|
+
*
|
|
66
|
+
* @param engineDir - Absolute engine directory
|
|
67
|
+
* @param launchablePath - Engine-relative launchable binary path
|
|
68
|
+
*/
|
|
69
|
+
export async function readLinkedAt(engineDir, launchablePath) {
|
|
70
|
+
const binary = join(engineDir, launchablePath);
|
|
71
|
+
const candidates = [binary, ...LINKED_LIBRARY_NAMES.map((name) => join(dirname(binary), name))];
|
|
72
|
+
let newest;
|
|
73
|
+
for (const path of candidates) {
|
|
74
|
+
try {
|
|
75
|
+
// stat follows the dist/bin symlinks to the linked objdir file.
|
|
76
|
+
const { mtimeMs } = await stat(path);
|
|
77
|
+
if (newest === undefined || mtimeMs > newest.mtimeMs)
|
|
78
|
+
newest = { mtimeMs, path };
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// A platform's other library names are simply absent.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return newest;
|
|
85
|
+
}
|
|
@@ -247,9 +247,7 @@ async function probeWithLsof(port) {
|
|
|
247
247
|
catch {
|
|
248
248
|
// `lsof` missing, or its stdout did not parse. Either way the port state
|
|
249
249
|
// is unknown, and an unknown port is reported as free so the probe never
|
|
250
|
-
// blocks a run on its own uncertainty.
|
|
251
|
-
// here returned the same value as this fallthrough, so it decided
|
|
252
|
-
// nothing.)
|
|
250
|
+
// blocks a run on its own uncertainty.
|
|
253
251
|
return { inUse: false };
|
|
254
252
|
}
|
|
255
253
|
}
|
|
@@ -77,11 +77,12 @@ export function getRules(binaryName, createManifest = false) {
|
|
|
77
77
|
// scaffolded and wired via XPCSHELL_TESTS_MANIFESTS.
|
|
78
78
|
pattern: /^(?!browser\/base\/content\/test\/)(.+)\/(test_[^/]+\.js)$/,
|
|
79
79
|
isRegistered: (engineDir, dirRel, fileName) => isXpcshellTestRegistered(engineDir, dirRel, fileName),
|
|
80
|
-
register: (engineDir,
|
|
80
|
+
register: (engineDir, after, dryRun, dirRel, fileName) => registerXpcshellTest(engineDir, dirRel, fileName, dryRun, createManifest, after),
|
|
81
81
|
// Both groups are guaranteed by the pattern, so slice avoids dead
|
|
82
82
|
// fallback branches.
|
|
83
83
|
extractArgs: (m) => m.slice(1, 3),
|
|
84
|
-
|
|
84
|
+
// `--after <entry>` places the section after that entry's block.
|
|
85
|
+
supportsAfter: true,
|
|
85
86
|
},
|
|
86
87
|
];
|
|
87
88
|
}
|
|
@@ -6,15 +6,25 @@
|
|
|
6
6
|
* objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
|
|
7
7
|
* never compiled: the run tests the unmutated binary and reports
|
|
8
8
|
* `control=missed`, which reads as a test that cannot fail. Such a target
|
|
9
|
-
* is
|
|
9
|
+
* is refused without `--build`, and with `--build` the control is
|
|
10
10
|
* applied before the build so the binary under test carries it. That build
|
|
11
11
|
* is a full `mach build`: the pre-test build is otherwise `mach build
|
|
12
|
-
* faster`, which compiles nothing, so the control still
|
|
12
|
+
* faster`, which compiles nothing, so the control would still miss.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Whether an engine-relative path has a compiled-source extension: a file
|
|
16
|
+
* the build compiles or code-generates into the binary. This is the
|
|
17
|
+
* extension test only. {@link findCompiledControlTargets} additionally
|
|
18
|
+
* drops non-header files an install manifest ships as-is.
|
|
19
|
+
*
|
|
20
|
+
* @param path - Engine-relative path
|
|
21
|
+
*/
|
|
22
|
+
export declare function isCompiledSourcePath(path: string): boolean;
|
|
14
23
|
/**
|
|
15
24
|
* The engine-relative targets that reach the runtime only through a
|
|
16
25
|
* compile: a compiled-source extension and no install-manifest entry that
|
|
17
|
-
* ships the file itself.
|
|
26
|
+
* ships the file itself. Headers are always compiled, even when an
|
|
27
|
+
* `EXPORTS` manifest installs them into `dist/include`.
|
|
18
28
|
*
|
|
19
29
|
* @param engineDir - Absolute engine directory
|
|
20
30
|
* @param enginePaths - The control's engine-relative targets
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
|
|
8
8
|
* never compiled: the run tests the unmutated binary and reports
|
|
9
9
|
* `control=missed`, which reads as a test that cannot fail. Such a target
|
|
10
|
-
* is
|
|
10
|
+
* is refused without `--build`, and with `--build` the control is
|
|
11
11
|
* applied before the build so the binary under test carries it. That build
|
|
12
12
|
* is a full `mach build`: the pre-test build is otherwise `mach build
|
|
13
|
-
* faster`, which compiles nothing, so the control still
|
|
13
|
+
* faster`, which compiles nothing, so the control would still miss.
|
|
14
14
|
*/
|
|
15
15
|
import { extname } from 'node:path';
|
|
16
16
|
import { classifyRuntimeDelivery } from './objdir-install-manifests.js';
|
|
@@ -34,24 +34,46 @@ const COMPILED_EXTENSIONS = new Set([
|
|
|
34
34
|
'.yaml',
|
|
35
35
|
'.yml',
|
|
36
36
|
]);
|
|
37
|
+
/**
|
|
38
|
+
* Header extensions. An `EXPORTS` install manifest names many headers (it
|
|
39
|
+
* copies them into `dist/include`), but that copy only feeds other
|
|
40
|
+
* compiles: the runtime never reads a header, so a header edit reaches the
|
|
41
|
+
* binary through a compile whatever the manifests say.
|
|
42
|
+
*/
|
|
43
|
+
const HEADER_EXTENSIONS = new Set(['.h', '.hh', '.hpp', '.ipdlh']);
|
|
44
|
+
/**
|
|
45
|
+
* Whether an engine-relative path has a compiled-source extension: a file
|
|
46
|
+
* the build compiles or code-generates into the binary. This is the
|
|
47
|
+
* extension test only. {@link findCompiledControlTargets} additionally
|
|
48
|
+
* drops non-header files an install manifest ships as-is.
|
|
49
|
+
*
|
|
50
|
+
* @param path - Engine-relative path
|
|
51
|
+
*/
|
|
52
|
+
export function isCompiledSourcePath(path) {
|
|
53
|
+
return COMPILED_EXTENSIONS.has(extname(path));
|
|
54
|
+
}
|
|
37
55
|
/**
|
|
38
56
|
* The engine-relative targets that reach the runtime only through a
|
|
39
57
|
* compile: a compiled-source extension and no install-manifest entry that
|
|
40
|
-
* ships the file itself.
|
|
58
|
+
* ships the file itself. Headers are always compiled, even when an
|
|
59
|
+
* `EXPORTS` manifest installs them into `dist/include`.
|
|
41
60
|
*
|
|
42
61
|
* @param engineDir - Absolute engine directory
|
|
43
62
|
* @param enginePaths - The control's engine-relative targets
|
|
44
63
|
*/
|
|
45
64
|
export async function findCompiledControlTargets(engineDir, enginePaths) {
|
|
46
|
-
const candidates = enginePaths.filter(
|
|
65
|
+
const candidates = enginePaths.filter(isCompiledSourcePath);
|
|
47
66
|
if (candidates.length === 0)
|
|
48
67
|
return [];
|
|
68
|
+
if (candidates.every((path) => HEADER_EXTENSIONS.has(extname(path))))
|
|
69
|
+
return candidates;
|
|
49
70
|
const index = await loadEngineInstallIndex(engineDir);
|
|
50
71
|
if (index === undefined)
|
|
51
72
|
return candidates;
|
|
52
73
|
const compiled = [];
|
|
53
74
|
for (const path of candidates) {
|
|
54
|
-
if ((
|
|
75
|
+
if (HEADER_EXTENSIONS.has(extname(path)) ||
|
|
76
|
+
(await classifyRuntimeDelivery(index, engineDir, path)) === 'unlisted') {
|
|
55
77
|
compiled.push(path);
|
|
56
78
|
}
|
|
57
79
|
}
|
|
@@ -70,8 +70,8 @@ export function parseControlDiff(content, options = {}) {
|
|
|
70
70
|
refuse('no diff sections found. Write the control with git, e.g. ' +
|
|
71
71
|
'`git -C engine diff -- <file> > control.diff` after making the edit, then revert the edit.');
|
|
72
72
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
73
|
+
const files = new Map();
|
|
74
|
+
for (const section of sections) {
|
|
75
75
|
let path = normalizePathSlashes(section.targetPath);
|
|
76
76
|
if (section.isBinary)
|
|
77
77
|
refuse(`${path} is a binary section.`);
|
|
@@ -90,11 +90,36 @@ export function parseControlDiff(content, options = {}) {
|
|
|
90
90
|
}
|
|
91
91
|
if (section.hunks.length === 0)
|
|
92
92
|
refuse(`${path} has no hunks.`);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const existing = files.get(path);
|
|
94
|
+
if (existing === undefined) {
|
|
95
|
+
files.set(path, { path, hunks: [...section.hunks], sections: 1 });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
existing.hunks.push(...section.hunks);
|
|
99
|
+
existing.sections += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...files.values()].map(({ path, hunks, sections: count }) => ({
|
|
103
|
+
path,
|
|
104
|
+
hunks: count === 1 ? hunks : mergeSectionHunks(path, hunks),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Orders the hunks of a file that several sections name (a generator that
|
|
109
|
+
* writes one section per edit) as one section's would be. Hunks that
|
|
110
|
+
* overlap cannot be applied one after another, so they are refused.
|
|
111
|
+
*/
|
|
112
|
+
function mergeSectionHunks(path, hunks) {
|
|
113
|
+
const ordered = [...hunks].sort((left, right) => left.oldStart - right.oldStart);
|
|
114
|
+
ordered.forEach((hunk, index) => {
|
|
115
|
+
const previous = ordered[index - 1];
|
|
116
|
+
if (previous !== undefined && previous.oldStart + previous.oldCount > hunk.oldStart) {
|
|
117
|
+
refuse(`${path} appears in more than one section, and hunks from them overlap ` +
|
|
118
|
+
`(@@ -${previous.oldStart},${previous.oldCount} @@ and @@ -${hunk.oldStart},${hunk.oldCount} @@). ` +
|
|
119
|
+
'Make every edit to the file, then write one section for it with `git -C engine diff -- <file>`.');
|
|
120
|
+
}
|
|
97
121
|
});
|
|
122
|
+
return ordered;
|
|
98
123
|
}
|
|
99
124
|
function hunkSides(hunk, label, index) {
|
|
100
125
|
const oldLines = [];
|