@hominis/fireforge 0.52.0 → 0.53.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 +26 -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.js +13 -9
- 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-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/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 +28 -0
- 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 +7 -8
- package/dist/src/commands/test-prelock.js +15 -12
- package/dist/src/commands/test-register.js +2 -2
- 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 +10 -0
- 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/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 +26 -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 +21 -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 +129 -24
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +105 -46
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `re-export --expect-removed` drops the staged declarations whose
|
|
4
|
+
* `creates` path the run retires.
|
|
5
|
+
*
|
|
6
|
+
* Retiring a file would otherwise leave another patch's staged
|
|
7
|
+
* registration (or forward import) for it in place, unflagged until the
|
|
8
|
+
* per-patch lint after both re-exports reported `staged-dependency-unused`.
|
|
9
|
+
* The run that retires the path drops every such declaration, in any patch of the queue, in one manifest
|
|
10
|
+
* update, and names each one.
|
|
11
|
+
*/
|
|
12
|
+
import { withPatchDirectoryLock } from '../core/patch-lock.js';
|
|
13
|
+
import { loadPatchesManifestForWrite, mutatePatchRowsInManifest } from '../core/patch-manifest.js';
|
|
14
|
+
import { info } from '../utils/logger.js';
|
|
15
|
+
import { recordStagedDeclarationPrune, } from './re-export-stale-registrations.js';
|
|
16
|
+
/**
|
|
17
|
+
* Finds the declarations whose `creates` path is retired and owned by no
|
|
18
|
+
* patch outside `retiring`.
|
|
19
|
+
*
|
|
20
|
+
* @param manifest - The queue to judge
|
|
21
|
+
* @param removed - The run's `--expect-removed` paths
|
|
22
|
+
* @param retiring - Patches whose ownership of a removed path does not
|
|
23
|
+
* count: the selected owners in a dry run, whose rows are not rewritten
|
|
24
|
+
* yet; nothing after the write, where a failed owner still owns its path
|
|
25
|
+
*/
|
|
26
|
+
function planRetiredDeclarationDrops(manifest, removed, retiring) {
|
|
27
|
+
const orphaned = new Set(removed.filter((file) => !manifest.patches.some((patch) => !retiring.has(patch.filename) && patch.filesAffected.includes(file))));
|
|
28
|
+
if (orphaned.size === 0)
|
|
29
|
+
return [];
|
|
30
|
+
const drops = [];
|
|
31
|
+
for (const patch of manifest.patches) {
|
|
32
|
+
const drop = planHolderDrop(patch, orphaned);
|
|
33
|
+
if (drop !== undefined)
|
|
34
|
+
drops.push(drop);
|
|
35
|
+
}
|
|
36
|
+
return drops;
|
|
37
|
+
}
|
|
38
|
+
function planHolderDrop(patch, orphaned) {
|
|
39
|
+
const forwardImports = patch.stagedDependencies?.forwardImports ?? [];
|
|
40
|
+
const registrations = patch.stagedDependencies?.registrations ?? [];
|
|
41
|
+
const dropped = [
|
|
42
|
+
...registrations
|
|
43
|
+
.filter((entry) => orphaned.has(entry.creates))
|
|
44
|
+
.map((entry) => ({ kind: 'registration', ...entry })),
|
|
45
|
+
...forwardImports
|
|
46
|
+
.filter((entry) => orphaned.has(entry.creates))
|
|
47
|
+
.map((entry) => ({ kind: 'import', ...entry })),
|
|
48
|
+
];
|
|
49
|
+
if (dropped.length === 0)
|
|
50
|
+
return undefined;
|
|
51
|
+
const keptImports = forwardImports.filter((entry) => !orphaned.has(entry.creates));
|
|
52
|
+
const keptRegistrations = registrations.filter((entry) => !orphaned.has(entry.creates));
|
|
53
|
+
const staged = {
|
|
54
|
+
...(keptImports.length > 0 ? { forwardImports: keptImports } : {}),
|
|
55
|
+
...(keptRegistrations.length > 0 ? { registrations: keptRegistrations } : {}),
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
holder: patch.filename,
|
|
59
|
+
dropped,
|
|
60
|
+
stagedDependencies: Object.keys(staged).length > 0 ? staged : undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function reportRetiredDeclarationDrops(drops, isDryRun) {
|
|
64
|
+
const verb = isDryRun ? 'would drop' : 'drops';
|
|
65
|
+
for (const { holder, dropped } of drops) {
|
|
66
|
+
for (const entry of dropped) {
|
|
67
|
+
const what = entry.kind === 'registration'
|
|
68
|
+
? `staged registration ${entry.file} "${entry.line.trim()}"`
|
|
69
|
+
: `staged forward-import ${entry.file} "${entry.specifier}"`;
|
|
70
|
+
info(`${holder}: ${verb} ${what}: --expect-removed retires ${entry.creates}.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Drops (or, in a dry run, names) every staged declaration whose `creates`
|
|
76
|
+
* path this run retired. Runs after the selected patches are written, so a
|
|
77
|
+
* path whose owner failed to re-export is still owned and its declarations
|
|
78
|
+
* stay.
|
|
79
|
+
*/
|
|
80
|
+
export async function dropRetiredPathDeclarations(args) {
|
|
81
|
+
const { manifest, isDryRun } = args;
|
|
82
|
+
const patchesDir = args.paths.patches;
|
|
83
|
+
const removed = args.options.expectRemoved ?? [];
|
|
84
|
+
const selected = new Set(args.selectedPatches.map((patch) => patch.filename));
|
|
85
|
+
if (removed.length === 0)
|
|
86
|
+
return;
|
|
87
|
+
if (isDryRun) {
|
|
88
|
+
reportRetiredDeclarationDrops(planRetiredDeclarationDrops(manifest, removed, selected), isDryRun);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const drops = await withPatchDirectoryLock(patchesDir, async () => {
|
|
92
|
+
const current = await loadPatchesManifestForWrite(patchesDir);
|
|
93
|
+
if (!current)
|
|
94
|
+
return [];
|
|
95
|
+
const planned = planRetiredDeclarationDrops(current, removed, new Set());
|
|
96
|
+
const byHolder = new Map(planned.map((drop) => [drop.holder, drop]));
|
|
97
|
+
if (byHolder.size === 0)
|
|
98
|
+
return [];
|
|
99
|
+
await mutatePatchRowsInManifest(patchesDir, [...byHolder.keys()], (existing) => {
|
|
100
|
+
const drop = byHolder.get(existing.filename);
|
|
101
|
+
if (drop === undefined)
|
|
102
|
+
return null;
|
|
103
|
+
return drop.stagedDependencies === undefined
|
|
104
|
+
? { unset: ['stagedDependencies'] }
|
|
105
|
+
: { set: { stagedDependencies: drop.stagedDependencies } };
|
|
106
|
+
});
|
|
107
|
+
return planned;
|
|
108
|
+
}, { command: 're-export' });
|
|
109
|
+
reportRetiredDeclarationDrops(drops, isDryRun);
|
|
110
|
+
for (const drop of drops) {
|
|
111
|
+
await recordStagedDeclarationPrune(patchesDir, drop.holder, drop.dropped, 'staged declarations on retired paths');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -23,7 +23,7 @@ import { reportForeignDrift } from './re-export-drift.js';
|
|
|
23
23
|
import { buildHunkSelectedDiff, hunkSelectionOf } from './re-export-hunks.js';
|
|
24
24
|
import { assertRefreshedBodyHasNoNewQueueErrors, lintReExportedPatch, refreshQueueCtxEntry, storeReExportLintResult, } from './re-export-lint.js';
|
|
25
25
|
import { assertScanAdoptionsHaveNoForwardImports, assertScanFileAdditionsHaveDiffHunks, confirmBroadScanAdditions, scanPatchFilesForReExport, } from './re-export-scan.js';
|
|
26
|
-
import { planStaleRegistrationDrop, recordStaleRegistrationDrop, reportStaleRegistrationDrop, } from './re-export-stale-registrations.js';
|
|
26
|
+
import { findPendingRegistrationCreates, planStaleRegistrationDrop, recordStaleRegistrationDrop, reportStaleRegistrationDrop, } from './re-export-stale-registrations.js';
|
|
27
27
|
const GIT_INDEX_LOCK_RETRY_MS = 300;
|
|
28
28
|
/**
|
|
29
29
|
* Retries a single patch re-export exactly once when the failure is
|
|
@@ -211,7 +211,7 @@ async function reExportSinglePatch(args) {
|
|
|
211
211
|
return false;
|
|
212
212
|
}
|
|
213
213
|
const { updates, ignoreChecks, effectiveTier, existingIgnoreSet, flagIgnoreSet } = computeReExportUpdates(patch, options, currentFilesAffected);
|
|
214
|
-
const stalePlan = planStaleRegistrationDrop(manifest, patch, currentFilesAffected);
|
|
214
|
+
const stalePlan = planStaleRegistrationDrop(manifest, patch, currentFilesAffected, await findPendingRegistrationCreates(paths.engine, manifest, patch, currentFilesAffected, adjacentCtx.expectedNew));
|
|
215
215
|
if (stalePlan !== undefined)
|
|
216
216
|
reportStaleRegistrationDrop(patch.filename, stalePlan, isDryRun);
|
|
217
217
|
const unset = applyStaleRegistrationDrop(stalePlan, updates);
|
|
@@ -1,18 +1,39 @@
|
|
|
1
|
-
import type { PatchesManifest, PatchMetadata, PatchStagedDependencies, PatchStagedRegistration } from '../types/commands/index.js';
|
|
1
|
+
import type { PatchesManifest, PatchMetadata, PatchStagedDependencies, PatchStagedForwardImport, PatchStagedRegistration } from '../types/commands/index.js';
|
|
2
2
|
/** The holder's registrations split by whether the queue still owns `creates`. */
|
|
3
3
|
export interface StaleRegistrationPlan {
|
|
4
4
|
/** Registrations whose `creates` path no patch owns. */
|
|
5
5
|
dropped: PatchStagedRegistration[];
|
|
6
|
+
/** Unowned registrations kept because their `creates` file is pending adoption. */
|
|
7
|
+
pending: PatchStagedRegistration[];
|
|
6
8
|
/** The staged block to write, or undefined when nothing is left. */
|
|
7
9
|
stagedDependencies: PatchStagedDependencies | undefined;
|
|
8
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* The unowned `creates` paths of `patch`'s registrations that are pending
|
|
13
|
+
* adoption: the file exists in `engine/`, or is an `--expect-new` path of
|
|
14
|
+
* this run. Such a registration is waiting for its subject patch's scan.
|
|
15
|
+
*/
|
|
16
|
+
export declare function findPendingRegistrationCreates(engineDir: string, manifest: PatchesManifest, patch: PatchMetadata, currentFilesAffected: readonly string[], expectedNew: ReadonlySet<string>): Promise<Set<string>>;
|
|
9
17
|
/**
|
|
10
18
|
* Finds `patch`'s registrations whose `creates` path no patch in the queue
|
|
11
|
-
* owns, judging `patch` by the file list this re-export will write.
|
|
12
|
-
*
|
|
19
|
+
* owns, judging `patch` by the file list this re-export will write. A
|
|
20
|
+
* registration whose `creates` path is in `pending` (see
|
|
21
|
+
* {@link findPendingRegistrationCreates}) is kept and reported instead.
|
|
22
|
+
* Returns undefined when nothing is stale or pending.
|
|
13
23
|
*/
|
|
14
|
-
export declare function planStaleRegistrationDrop(manifest: PatchesManifest, patch: PatchMetadata, currentFilesAffected: readonly string[]): StaleRegistrationPlan | undefined;
|
|
15
|
-
/** Names each dropped registration, in both a dry run and a write. */
|
|
24
|
+
export declare function planStaleRegistrationDrop(manifest: PatchesManifest, patch: PatchMetadata, currentFilesAffected: readonly string[], pendingCreates?: ReadonlySet<string>): StaleRegistrationPlan | undefined;
|
|
25
|
+
/** Names each dropped and each kept-pending registration, in both a dry run and a write. */
|
|
16
26
|
export declare function reportStaleRegistrationDrop(patchFilename: string, plan: StaleRegistrationPlan, isDryRun: boolean): void;
|
|
17
|
-
/**
|
|
27
|
+
/** One declaration a re-export pruned, as the history log records it. */
|
|
28
|
+
export type PrunedStagedDeclaration = ({
|
|
29
|
+
kind: 'registration';
|
|
30
|
+
} & PatchStagedRegistration) | ({
|
|
31
|
+
kind: 'import';
|
|
32
|
+
} & PatchStagedForwardImport);
|
|
33
|
+
/**
|
|
34
|
+
* Records declarations a re-export pruned from one patch as a delta, like
|
|
35
|
+
* `staged-dependency --remove`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function recordStagedDeclarationPrune(patchesDir: string, patchFilename: string, removed: readonly PrunedStagedDeclaration[], what: string): Promise<void>;
|
|
38
|
+
/** Records the dropped registrations as a delta, like `staged-dependency --remove`. */
|
|
18
39
|
export declare function recordStaleRegistrationDrop(patchesDir: string, patchFilename: string, plan: StaleRegistrationPlan): Promise<void>;
|
|
@@ -3,23 +3,40 @@
|
|
|
3
3
|
* Re-export prunes a staged registration the queue can no longer satisfy.
|
|
4
4
|
*
|
|
5
5
|
* A merge that deletes a file whose jar.mn line lives in another patch
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* leaves the staged registration on that patch: lint warns
|
|
7
|
+
* `staged-dependency-unused`, and the stale declaration keeps the holder
|
|
8
|
+
* measuring above a just-lowered size ceiling. When no patch in
|
|
9
9
|
* the queue owns a registration's `creates` path any more, the declaration
|
|
10
10
|
* cannot be met, so the holder's own re-export drops it and says so.
|
|
11
|
+
*
|
|
12
|
+
* A registration whose `creates` file exists in `engine/`, or is an
|
|
13
|
+
* `--expect-new` path of the run, is kept: a manifest owner's re-export can
|
|
14
|
+
* run between declaring the registration and the scan that adopts the
|
|
15
|
+
* file, and pruning it there would break the scan that needs it next.
|
|
11
16
|
*/
|
|
17
|
+
import { join } from 'node:path';
|
|
12
18
|
import { appendHistoryBestEffort } from '../core/history-log.js';
|
|
19
|
+
import { pathExists } from '../utils/fs.js';
|
|
13
20
|
import { info } from '../utils/logger.js';
|
|
14
21
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
22
|
+
* The unowned `creates` paths of `patch`'s registrations that are pending
|
|
23
|
+
* adoption: the file exists in `engine/`, or is an `--expect-new` path of
|
|
24
|
+
* this run. Such a registration is waiting for its subject patch's scan.
|
|
18
25
|
*/
|
|
19
|
-
export function
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
export async function findPendingRegistrationCreates(engineDir, manifest, patch, currentFilesAffected, expectedNew) {
|
|
27
|
+
const owned = ownedPaths(manifest, patch, currentFilesAffected);
|
|
28
|
+
const pending = new Set();
|
|
29
|
+
for (const registration of patch.stagedDependencies?.registrations ?? []) {
|
|
30
|
+
const creates = registration.creates;
|
|
31
|
+
if (owned.has(creates) || pending.has(creates))
|
|
32
|
+
continue;
|
|
33
|
+
if (expectedNew.has(creates) || (await pathExists(join(engineDir, creates)))) {
|
|
34
|
+
pending.add(creates);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return pending;
|
|
38
|
+
}
|
|
39
|
+
function ownedPaths(manifest, patch, currentFilesAffected) {
|
|
23
40
|
const owned = new Set(currentFilesAffected);
|
|
24
41
|
for (const entry of manifest.patches) {
|
|
25
42
|
if (entry.filename === patch.filename)
|
|
@@ -27,10 +44,26 @@ export function planStaleRegistrationDrop(manifest, patch, currentFilesAffected)
|
|
|
27
44
|
for (const file of entry.filesAffected)
|
|
28
45
|
owned.add(file);
|
|
29
46
|
}
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
return owned;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Finds `patch`'s registrations whose `creates` path no patch in the queue
|
|
51
|
+
* owns, judging `patch` by the file list this re-export will write. A
|
|
52
|
+
* registration whose `creates` path is in `pending` (see
|
|
53
|
+
* {@link findPendingRegistrationCreates}) is kept and reported instead.
|
|
54
|
+
* Returns undefined when nothing is stale or pending.
|
|
55
|
+
*/
|
|
56
|
+
export function planStaleRegistrationDrop(manifest, patch, currentFilesAffected, pendingCreates = new Set()) {
|
|
57
|
+
const registrations = patch.stagedDependencies?.registrations ?? [];
|
|
58
|
+
if (registrations.length === 0)
|
|
32
59
|
return undefined;
|
|
33
|
-
const
|
|
60
|
+
const owned = ownedPaths(manifest, patch, currentFilesAffected);
|
|
61
|
+
const unowned = registrations.filter((registration) => !owned.has(registration.creates));
|
|
62
|
+
const pending = unowned.filter((registration) => pendingCreates.has(registration.creates));
|
|
63
|
+
const dropped = unowned.filter((registration) => !pendingCreates.has(registration.creates));
|
|
64
|
+
if (dropped.length === 0 && pending.length === 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
const kept = registrations.filter((registration) => !dropped.includes(registration));
|
|
34
67
|
const forwardImports = patch.stagedDependencies?.forwardImports ?? [];
|
|
35
68
|
const staged = {
|
|
36
69
|
...(forwardImports.length > 0 ? { forwardImports } : {}),
|
|
@@ -38,25 +71,36 @@ export function planStaleRegistrationDrop(manifest, patch, currentFilesAffected)
|
|
|
38
71
|
};
|
|
39
72
|
return {
|
|
40
73
|
dropped,
|
|
74
|
+
pending,
|
|
41
75
|
stagedDependencies: Object.keys(staged).length > 0 ? staged : undefined,
|
|
42
76
|
};
|
|
43
77
|
}
|
|
44
|
-
/** Names each dropped registration, in both a dry run and a write. */
|
|
78
|
+
/** Names each dropped and each kept-pending registration, in both a dry run and a write. */
|
|
45
79
|
export function reportStaleRegistrationDrop(patchFilename, plan, isDryRun) {
|
|
46
80
|
const verb = isDryRun ? 'would drop' : 'drops';
|
|
47
81
|
for (const registration of plan.dropped) {
|
|
48
82
|
info(`${patchFilename}: ${verb} staged registration ${registration.file} "${registration.line.trim()}": ` +
|
|
49
83
|
`no patch in the queue creates ${registration.creates} any more.`);
|
|
50
84
|
}
|
|
85
|
+
for (const registration of plan.pending) {
|
|
86
|
+
info(`${patchFilename}: staged registration ${registration.file} "${registration.line.trim()}" ` +
|
|
87
|
+
`for ${registration.creates} kept: file exists but no patch owns it yet.`);
|
|
88
|
+
}
|
|
51
89
|
}
|
|
52
|
-
/**
|
|
53
|
-
export
|
|
90
|
+
/**
|
|
91
|
+
* Records declarations a re-export pruned from one patch as a delta, like
|
|
92
|
+
* `staged-dependency --remove`.
|
|
93
|
+
*/
|
|
94
|
+
export async function recordStagedDeclarationPrune(patchesDir, patchFilename, removed, what) {
|
|
95
|
+
if (removed.length === 0)
|
|
96
|
+
return;
|
|
54
97
|
await appendHistoryBestEffort(patchesDir, {
|
|
55
98
|
operation: 're-export-staged-prune',
|
|
56
|
-
args: {
|
|
57
|
-
filename: patchFilename,
|
|
58
|
-
removed: plan.dropped.map((registration) => ({ kind: 'registration', ...registration })),
|
|
59
|
-
},
|
|
99
|
+
args: { filename: patchFilename, removed: [...removed] },
|
|
60
100
|
result: 'ok',
|
|
61
|
-
}, `re-export pruned
|
|
101
|
+
}, `re-export pruned ${what} on ${patchFilename}`);
|
|
102
|
+
}
|
|
103
|
+
/** Records the dropped registrations as a delta, like `staged-dependency --remove`. */
|
|
104
|
+
export async function recordStaleRegistrationDrop(patchesDir, patchFilename, plan) {
|
|
105
|
+
await recordStagedDeclarationPrune(patchesDir, patchFilename, plan.dropped.map((registration) => ({ kind: 'registration', ...registration })), 'stale staged registrations');
|
|
62
106
|
}
|
|
@@ -17,6 +17,7 @@ import { reExportFilesInPlace } from './re-export-files.js';
|
|
|
17
17
|
import { buildReExportLintContext, finishReExportLint } from './re-export-lint.js';
|
|
18
18
|
import { applyReExportFilesPositionalFolding, validateReExportOptionCombinations, } from './re-export-options.js';
|
|
19
19
|
import { validateExpectedRemovals } from './re-export-removed.js';
|
|
20
|
+
import { dropRetiredPathDeclarations } from './re-export-retired-declarations.js';
|
|
20
21
|
import { normalizeEngineRelativeInput, normalizeScanFiles } from './re-export-scan.js';
|
|
21
22
|
import { assignScanFileOwners, scanOptionsForPatch } from './re-export-scan-owner.js';
|
|
22
23
|
import { reExportSinglePatchWithIndexLockRetry, } from './re-export-single.js';
|
|
@@ -190,6 +191,9 @@ export async function reExportCommand(projectRoot, patches, options) {
|
|
|
190
191
|
progress.error('Re-export failed');
|
|
191
192
|
throw new GeneralError('All selected patches failed to re-export. Check the errors above.');
|
|
192
193
|
}
|
|
194
|
+
// Declarations on a path this run retired can never be met; drop them
|
|
195
|
+
// now rather than leave them for the next per-patch lint to report.
|
|
196
|
+
await dropRetiredPathDeclarations({ paths, manifest, options, selectedPatches, isDryRun });
|
|
193
197
|
// `--stamp` only fires on a run where every selected patch refreshed
|
|
194
198
|
// cleanly. A partial success would leave some patches with a stale body
|
|
195
199
|
// but a new version, the opposite of the "what I tested, what the
|
|
@@ -21,9 +21,9 @@ import { runPatchLoop } from './patch-loop.js';
|
|
|
21
21
|
*/
|
|
22
22
|
export async function handleContinue(projectRoot, maxFuzz, waitLockSeconds) {
|
|
23
23
|
intro('FireForge Rebase — Continue');
|
|
24
|
-
// A present-but-unreadable session is not "no session in progress"
|
|
25
|
-
//
|
|
26
|
-
// point at --abort, which can
|
|
24
|
+
// A present-but-unreadable session is not "no session in progress":
|
|
25
|
+
// treating it as one leaves a corrupt file unrecoverable. Name the file
|
|
26
|
+
// and point at --abort, which can clear it.
|
|
27
27
|
const read = await readRebaseSession(projectRoot);
|
|
28
28
|
if (!read.present)
|
|
29
29
|
throw new NoRebaseSessionError();
|
|
@@ -32,9 +32,9 @@ export async function registerCommand(projectRoot, filePath, options = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
// Accept either repo-root-relative (`engine/browser/...`) or
|
|
34
34
|
// engine-relative (`browser/...`) inputs. Operators frequently paste
|
|
35
|
-
// the former from the output of tab completion or `git status`,
|
|
36
|
-
//
|
|
37
|
-
//
|
|
35
|
+
// the former from the output of tab completion or `git status`, so
|
|
36
|
+
// both shapes resolve to the same path instead of a "File not found"
|
|
37
|
+
// error.
|
|
38
38
|
const engineRelativePath = stripEnginePrefix(filePath);
|
|
39
39
|
// Verify the file exists in engine/ (skip for dry-run)
|
|
40
40
|
if (!options.dryRun) {
|
|
@@ -97,7 +97,8 @@ export function registerRegister(program, { getProjectRoot, withErrorHandling })
|
|
|
97
97
|
.command('register <path>')
|
|
98
98
|
.description('Register a file in the appropriate build manifest')
|
|
99
99
|
.option('--dry-run', 'Show what would be changed without writing')
|
|
100
|
-
.option('--after <entry>', 'Place entry after line containing this substring (instead of alphabetical)'
|
|
100
|
+
.option('--after <entry>', 'Place entry after line containing this substring (instead of alphabetical); for an ' +
|
|
101
|
+
'xpcshell test, after the whole section of the xpcshell.toml entry it names')
|
|
101
102
|
.option('--create-manifest', 'Scaffold a missing manifest (directory moz.build with EXTRA_JS_MODULES / XPCSHELL_TESTS_MANIFESTS, or xpcshell.toml) and wire the parent DIRS chain instead of failing with "Manifest not found"')
|
|
102
103
|
.action(withErrorHandling(async (path, options) => {
|
|
103
104
|
await registerCommand(getProjectRoot(), path, pickDefined(options));
|
|
@@ -409,7 +409,7 @@ export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
|
|
|
409
409
|
.option('--fail-on <classifications>', 'Comma-separated classification list that fails --check, replacing the default set (implies --check). Valid: patch-backed, patch-owned-drift, unmanaged, branding, furnace, conflict, binary-unsupported, ownership-conflict (a path claimed by more than one patch)')
|
|
410
410
|
.option('--allow <classifications>', 'Comma-separated classification list that never fails --check, removed from the fail set (implies --check; same values as --fail-on)')
|
|
411
411
|
.option('--fail-on-unknown', 'With --allow: also fail on every present classification that neither --fail-on nor --allow names, so a classification added in a later FireForge fails closed (implies --check)')
|
|
412
|
-
.option('--patches <names...>', 'Judge only the named patches: keeps the files each owns or claims and the ownership rows naming it (filename, stem, order number, manifest name, or slug; repeat or
|
|
412
|
+
.option('--patches <names...>', 'Judge only the named patches: keeps the files each owns or claims and the ownership rows naming it (filename, stem, order number, manifest name, or slug; repeat, or separate with commas or whitespace). Unmanaged files belong to no patch and drop out. Default view, --json and --ownership only')
|
|
413
413
|
.action(withErrorHandling(async (options) => {
|
|
414
414
|
await statusCommand(getProjectRoot(), options);
|
|
415
415
|
}));
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* runs `furnace apply` over the whole `components/` tree, so a
|
|
4
4
|
* `fireforge test --build` meant to capture the committed behaviour could
|
|
5
5
|
* silently ship component edits nobody had deployed yet (another session's
|
|
6
|
-
* included)
|
|
7
|
-
* ride the verdict line, and `--no-furnace-apply` packages `engine/` as it
|
|
6
|
+
* included). The applied names therefore ride the verdict line, and `--no-furnace-apply` packages `engine/` as it
|
|
8
7
|
* stands, naming the undeployed edits it left out.
|
|
9
8
|
*/
|
|
10
9
|
import type { BuildPreparation } from '../core/build-prepare.js';
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* runs `furnace apply` over the whole `components/` tree, so a
|
|
5
5
|
* `fireforge test --build` meant to capture the committed behaviour could
|
|
6
6
|
* silently ship component edits nobody had deployed yet (another session's
|
|
7
|
-
* included)
|
|
8
|
-
* ride the verdict line, and `--no-furnace-apply` packages `engine/` as it
|
|
7
|
+
* included). The applied names therefore ride the verdict line, and `--no-furnace-apply` packages `engine/` as it
|
|
9
8
|
* stands, naming the undeployed edits it left out.
|
|
10
9
|
*/
|
|
11
10
|
import { listStaleFurnaceComponents } from '../core/furnace-staleness.js';
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* the per-file line budget): the incremental protected mach build with
|
|
4
4
|
* its baseline write, and the `--build-only` union-build shape.
|
|
5
5
|
*/
|
|
6
|
+
import type { ControlBuildStamp } from '../core/build-baseline-types.js';
|
|
6
7
|
import type { TestOptions } from '../types/commands/index.js';
|
|
7
8
|
import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
|
|
8
9
|
import type { HarnessClassification } from './test-modes.js';
|
|
@@ -18,4 +19,11 @@ export declare function runTestBuildPhase(projectRoot: string, paths: ProjectPat
|
|
|
18
19
|
normalizedPaths: string[];
|
|
19
20
|
/** Forces a full pre-test build (a compiled negative control). */
|
|
20
21
|
fullBuildReason?: string;
|
|
22
|
+
/** Recorded on the pre-test build's baseline (a compiled negative control). */
|
|
23
|
+
controlBuild?: ControlBuildStamp;
|
|
24
|
+
/**
|
|
25
|
+
* Engine-relative launchable binary: anchors the stale-compiled check,
|
|
26
|
+
* which refuses a build-less run and escalates a `--build` one.
|
|
27
|
+
*/
|
|
28
|
+
launchablePath?: string | undefined;
|
|
21
29
|
}): Promise<boolean>;
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
import { readBuildBaseline, writeBuildBaseline } from '../core/build-baseline.js';
|
|
8
8
|
import { prepareBuildEnvironment } from '../core/build-prepare.js';
|
|
9
9
|
import { applyMozconfigAnchor, formatCoverageRetentionNotice, formatExtendCoverageRefusal, planCoverageRetention, recordScopedCoverage, } from '../core/coverage-extend.js';
|
|
10
|
+
import { normalizeRequestPath } from '../core/coverage-records.js';
|
|
10
11
|
import { hasBuildArtifacts } from '../core/mach.js';
|
|
11
12
|
import { runProtectedMachBuild, withBuildLock } from '../core/mach.js';
|
|
12
13
|
import { assertHealthyPartialConfig, pruneDanglingTestLinks } from '../core/objdir-maintenance.js';
|
|
13
14
|
import { buildHarnessCrashMessage } from '../core/test-harness-crash.js';
|
|
15
|
+
import { findCompiledSourcesNewerThanBinary, formatStaleCompiledBuildNotice, } from '../core/test-stale-check.js';
|
|
14
16
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
15
17
|
import { BuildError } from '../errors/build.js';
|
|
16
18
|
import { toError } from '../utils/errors.js';
|
|
@@ -27,7 +29,7 @@ import { emitPassVerdict } from './test-verdict.js';
|
|
|
27
29
|
* @param options - See {@link RunPreTestBuildOptions}
|
|
28
30
|
*/
|
|
29
31
|
async function runPreTestBuild(options) {
|
|
30
|
-
const { projectRoot, paths, projectConfig, harnessRetries, testPackagingCoverage, refuseUnexportedDrift, extend, furnace, fullBuildReason, } = options;
|
|
32
|
+
const { projectRoot, paths, projectConfig, harnessRetries, testPackagingCoverage, refuseUnexportedDrift, extend, furnace, fullBuildReason, controlBuild, } = options;
|
|
31
33
|
await withBuildLock(projectRoot, async () => {
|
|
32
34
|
// Pass the previous baseline exactly like `fireforge build` does, so
|
|
33
35
|
// auto-configure runs under the same conditions on both paths. The
|
|
@@ -121,6 +123,7 @@ async function runPreTestBuild(options) {
|
|
|
121
123
|
? 'carry-forward'
|
|
122
124
|
: 'auto',
|
|
123
125
|
buildKind,
|
|
126
|
+
controlBuild,
|
|
124
127
|
});
|
|
125
128
|
}
|
|
126
129
|
catch (baselineError) {
|
|
@@ -200,7 +203,7 @@ function describeBuildInvocation(extending, coverage) {
|
|
|
200
203
|
* next steps, and emits the run's single `PASS` verdict.
|
|
201
204
|
*/
|
|
202
205
|
export async function runTestBuildPhase(projectRoot, paths, projectConfig, harnessRetries, options, request) {
|
|
203
|
-
const { classification, normalizedPaths,
|
|
206
|
+
const { classification, normalizedPaths, controlBuild, launchablePath } = request;
|
|
204
207
|
const extending = options.extendCoverage === true;
|
|
205
208
|
if (extending) {
|
|
206
209
|
assertExtendCoverageUsage(options, normalizedPaths);
|
|
@@ -213,19 +216,28 @@ export async function runTestBuildPhase(projectRoot, paths, projectConfig, harne
|
|
|
213
216
|
// rebuild also cannot regenerate the compiled StaticComponents table,
|
|
214
217
|
// so it runs the components.conf gate up-front (the path-less shape
|
|
215
218
|
// refreshes the anchor itself and skips it).
|
|
216
|
-
|
|
217
|
-
|
|
219
|
+
// Coverage paths are recorded normalized (no trailing slash, no `./`):
|
|
220
|
+
// the baseline validator refuses the raw `dir/` CLI shape, which would
|
|
221
|
+
// block every later plain `fireforge test`. A request
|
|
222
|
+
// for the engine root (`.`) packages everything, so it records `'full'`.
|
|
223
|
+
const coveragePaths = normalizedPaths.map(normalizeRequestPath);
|
|
224
|
+
const coverage = coveragePaths.length === 0 || coveragePaths.includes('')
|
|
225
|
+
? 'full'
|
|
226
|
+
: [...new Set(coveragePaths)];
|
|
227
|
+
if (coverage !== 'full') {
|
|
218
228
|
const previousBaseline = await readBuildBaseline(projectRoot);
|
|
219
229
|
await enforceStaticComponentsGate(paths.engine, previousBaseline, options);
|
|
220
230
|
if (extending) {
|
|
221
231
|
// Staging-input half of the plan, before the build lock: a refusal
|
|
222
232
|
// here costs neither build time nor the lock.
|
|
223
|
-
const plan = await planCoverageRetention(paths.engine, previousBaseline,
|
|
233
|
+
const plan = await planCoverageRetention(paths.engine, previousBaseline, coverage);
|
|
224
234
|
if (plan.dropped.length > 0) {
|
|
225
235
|
throw new GeneralError(formatExtendCoverageRefusal(plan.dropped));
|
|
226
236
|
}
|
|
227
237
|
}
|
|
228
238
|
}
|
|
239
|
+
const fullBuildReason = request.fullBuildReason ??
|
|
240
|
+
(await describeCompiledEscalation(projectRoot, paths.engine, launchablePath));
|
|
229
241
|
await runPreTestBuild({
|
|
230
242
|
projectRoot,
|
|
231
243
|
paths,
|
|
@@ -233,8 +245,9 @@ export async function runTestBuildPhase(projectRoot, paths, projectConfig, harne
|
|
|
233
245
|
harnessRetries,
|
|
234
246
|
testPackagingCoverage: coverage,
|
|
235
247
|
refuseUnexportedDrift: options.refuseUnexportedDrift === true,
|
|
236
|
-
extend: extending ? { requestedPaths:
|
|
248
|
+
extend: extending && coverage !== 'full' ? { requestedPaths: coverage } : undefined,
|
|
237
249
|
fullBuildReason,
|
|
250
|
+
controlBuild,
|
|
238
251
|
furnace: await planPreTestFurnace(projectRoot, paths, projectConfig, options, normalizedPaths),
|
|
239
252
|
});
|
|
240
253
|
info('');
|
|
@@ -249,7 +262,29 @@ export async function runTestBuildPhase(projectRoot, paths, projectConfig, harne
|
|
|
249
262
|
}
|
|
250
263
|
}
|
|
251
264
|
else {
|
|
252
|
-
await enforceStaleBuildGate(projectRoot, paths.engine, options, normalizedPaths
|
|
265
|
+
await enforceStaleBuildGate(projectRoot, paths.engine, options, normalizedPaths, {
|
|
266
|
+
launchablePath,
|
|
267
|
+
});
|
|
253
268
|
}
|
|
254
269
|
return false;
|
|
255
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Why a `--build` run's pre-test build must be a full `mach build` although
|
|
273
|
+
* nothing asked for one: compiled sources changed after the binary was
|
|
274
|
+
* linked, or the binary still carries a negative control a killed run (or a
|
|
275
|
+
* failed restore build) left compiled in. `mach build faster` compiles
|
|
276
|
+
* nothing, so either would otherwise survive the build.
|
|
277
|
+
*/
|
|
278
|
+
async function describeCompiledEscalation(projectRoot, engineDir, launchablePath) {
|
|
279
|
+
const baseline = await readBuildBaseline(projectRoot);
|
|
280
|
+
const stale = await findCompiledSourcesNewerThanBinary(engineDir, baseline, launchablePath);
|
|
281
|
+
if (stale !== undefined && stale.paths.length > 0)
|
|
282
|
+
return formatStaleCompiledBuildNotice(stale);
|
|
283
|
+
const stamp = baseline?.controlBuild;
|
|
284
|
+
if (stamp !== undefined) {
|
|
285
|
+
return (`The binary was built with the negative control ${stamp.diffFile} applied and not ` +
|
|
286
|
+
'rebuilt since; escalating this pre-test build to a full mach build so it recompiles ' +
|
|
287
|
+
'the restored source.');
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The restore build after a compiled negative control.
|
|
3
|
+
*
|
|
4
|
+
* A control on compiled source is applied before a full pre-test build, so
|
|
5
|
+
* the binary under test carries it. Restoring the source byte-exact after
|
|
6
|
+
* the run does not unbuild it: the objdir binary keeps the mutation until a
|
|
7
|
+
* build recompiles the original. Every later `fireforge test` would then
|
|
8
|
+
* test the control. The orchestrator therefore runs one more full
|
|
9
|
+
* `mach build` right after a successful restore, still under the same
|
|
10
|
+
* engine-session hold, and its baseline drops the `controlBuild` stamp the
|
|
11
|
+
* control's build recorded. When that build fails, or the run never gets
|
|
12
|
+
* here (killed), the stamp stays and the next `fireforge test` refuses with
|
|
13
|
+
* `note=control-built` until `fireforge build` replaces it.
|
|
14
|
+
*/
|
|
15
|
+
import type { ControlBuildStamp } from '../core/build-baseline-types.js';
|
|
16
|
+
/** Outcome of {@link runControlRestoreBuild}, stamped as `control-restore-build=`. */
|
|
17
|
+
export type ControlRestoreBuildOutcome = 'done' | 'failed';
|
|
18
|
+
/**
|
|
19
|
+
* Whether the recorded baseline is the one this run's control build wrote:
|
|
20
|
+
* only then did a build compile the control in (a failed control build
|
|
21
|
+
* writes no baseline, and its restored source's fresh mtime trips the
|
|
22
|
+
* stale-compiled check instead).
|
|
23
|
+
*
|
|
24
|
+
* @param projectRoot - Project root
|
|
25
|
+
* @param stamp - The stamp this run's control build recorded
|
|
26
|
+
*/
|
|
27
|
+
export declare function controlBuildRecorded(projectRoot: string, stamp: ControlBuildStamp): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Rebuilds the binary after a compiled control was restored: a full
|
|
30
|
+
* protected `mach build` under the build lock, then a baseline without the
|
|
31
|
+
* stamp. The previous baseline's packaging-coverage claim is carried
|
|
32
|
+
* forward unchanged, since recompiling the restored source stages no tests.
|
|
33
|
+
* Never throws: a failure leaves the stamp in place, which is the refusal
|
|
34
|
+
* the next run needs.
|
|
35
|
+
*
|
|
36
|
+
* @param projectRoot - Project root
|
|
37
|
+
* @param stamp - The stamp this run's control build recorded
|
|
38
|
+
* @param harnessRetries - Recognized-crash retry budget for the build
|
|
39
|
+
*/
|
|
40
|
+
export declare function runControlRestoreBuild(projectRoot: string, stamp: ControlBuildStamp, harnessRetries: number | undefined): Promise<ControlRestoreBuildOutcome>;
|