@hominis/fireforge 0.32.0 → 0.34.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 +25 -0
- package/dist/src/commands/build.js +9 -1
- package/dist/src/commands/doctor-furnace-jar.d.ts +16 -0
- package/dist/src/commands/doctor-furnace-jar.js +47 -0
- package/dist/src/commands/doctor-furnace.js +2 -0
- package/dist/src/commands/export-flow.js +4 -1
- package/dist/src/commands/export-placement-gate.js +4 -1
- package/dist/src/commands/export.js +61 -4
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +20 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +52 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +10 -0
- package/dist/src/commands/furnace/chrome-doc.js +31 -4
- package/dist/src/commands/furnace/create-browser-test.d.ts +30 -0
- package/dist/src/commands/furnace/create-browser-test.js +180 -0
- package/dist/src/commands/furnace/create-mochikit.js +11 -4
- package/dist/src/commands/furnace/create-xpcshell.d.ts +1 -1
- package/dist/src/commands/furnace/create-xpcshell.js +38 -12
- package/dist/src/commands/furnace/create.js +7 -114
- package/dist/src/commands/furnace/index.js +6 -1
- package/dist/src/commands/furnace/scan.d.ts +1 -0
- package/dist/src/commands/furnace/scan.js +57 -27
- package/dist/src/commands/furnace/validate.js +17 -1
- package/dist/src/commands/patch/split-plan.d.ts +18 -2
- package/dist/src/commands/patch/split-plan.js +90 -16
- package/dist/src/commands/patch/split.js +12 -3
- package/dist/src/commands/re-export-options.d.ts +26 -0
- package/dist/src/commands/re-export-options.js +48 -1
- package/dist/src/commands/re-export.js +4 -1
- package/dist/src/commands/register.js +10 -1
- package/dist/src/commands/test-diagnose.js +12 -0
- package/dist/src/commands/test.js +29 -22
- package/dist/src/commands/token.js +12 -1
- package/dist/src/commands/typecheck.js +35 -0
- package/dist/src/core/build-prepare.js +23 -3
- package/dist/src/core/config-validate.js +26 -0
- package/dist/src/core/furnace-apply-dry-run.d.ts +17 -0
- package/dist/src/core/furnace-apply-dry-run.js +105 -0
- package/dist/src/core/furnace-apply-ftl.d.ts +12 -0
- package/dist/src/core/furnace-apply-ftl.js +97 -1
- package/dist/src/core/furnace-apply-helpers.js +10 -80
- package/dist/src/core/furnace-registration.d.ts +24 -0
- package/dist/src/core/furnace-registration.js +56 -3
- package/dist/src/core/furnace-validate-registration.js +18 -0
- package/dist/src/core/mach-resource-shim.d.ts +58 -0
- package/dist/src/core/mach-resource-shim.js +290 -0
- package/dist/src/core/mach.d.ts +51 -10
- package/dist/src/core/mach.js +76 -25
- package/dist/src/core/manifest-helpers.d.ts +13 -0
- package/dist/src/core/manifest-helpers.js +29 -4
- package/dist/src/core/manifest-rules.d.ts +13 -3
- package/dist/src/core/manifest-rules.js +44 -17
- package/dist/src/core/patch-export.d.ts +10 -0
- package/dist/src/core/patch-export.js +23 -2
- package/dist/src/core/patch-lint-cross.d.ts +31 -0
- package/dist/src/core/patch-lint-cross.js +83 -63
- package/dist/src/core/patch-lint-reexports.d.ts +1 -1
- package/dist/src/core/patch-lint-reexports.js +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-module.js +15 -2
- package/dist/src/core/register-result.d.ts +9 -0
- package/dist/src/core/register-scaffold.d.ts +55 -0
- package/dist/src/core/register-scaffold.js +146 -0
- package/dist/src/core/register-test-manifest.js +3 -1
- package/dist/src/core/register-xpcshell-test.d.ts +30 -0
- package/dist/src/core/register-xpcshell-test.js +96 -0
- package/dist/src/core/test-harness-crash.d.ts +32 -4
- package/dist/src/core/test-harness-crash.js +152 -14
- package/dist/src/core/token-dark-mode.d.ts +9 -0
- package/dist/src/core/token-dark-mode.js +1 -1
- package/dist/src/core/token-docs.d.ts +32 -0
- package/dist/src/core/token-docs.js +101 -0
- package/dist/src/core/token-manager.d.ts +8 -0
- package/dist/src/core/token-manager.js +77 -95
- package/dist/src/core/token-variant.d.ts +39 -0
- package/dist/src/core/token-variant.js +141 -0
- package/dist/src/core/typecheck.js +56 -28
- package/dist/src/core/xpcshell-appdir.d.ts +7 -0
- package/dist/src/core/xpcshell-appdir.js +12 -3
- package/dist/src/types/commands/options.d.ts +17 -0
- package/dist/src/types/config.d.ts +13 -0
- package/package.json +4 -4
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Resource-monitor degrade guard for mach dispatches.
|
|
4
|
+
*
|
|
5
|
+
* mozbuild's build resource monitor calls `psutil.virtual_memory()` at
|
|
6
|
+
* startup (`start_resource_recording`) and again later from
|
|
7
|
+
* `mozbuild.base._run_make`. On some hosts (psutil vs Darwin 27) those
|
|
8
|
+
* raise `RuntimeError: host_statistics64(HOST_VM_INFO64) syscall failed`,
|
|
9
|
+
* and a `SystemResourceMonitor.__init__` that fails partway leaves an
|
|
10
|
+
* instance without `poll_interval`, so later polling dies with
|
|
11
|
+
* `AttributeError: ... poll_interval`. Any of these aborts `mach build` /
|
|
12
|
+
* `mach build faster` before the compiler ever runs.
|
|
13
|
+
*
|
|
14
|
+
* 0.33.0 injected a `sitecustomize.py` via the mach subprocess PYTHONPATH.
|
|
15
|
+
* Field report (0.34.0 cycle): mach re-execs itself into its private
|
|
16
|
+
* virtualenv and drops PYTHONPATH, so the env-var route never loads and
|
|
17
|
+
* the crash family escaped on every protected entry point. FireForge now
|
|
18
|
+
* owns the guard in-process on its mach dispatches by installing a
|
|
19
|
+
* `fireforge_mach_guard.pth` + module pair directly into every discovered
|
|
20
|
+
* mach virtualenv site-packages directory: `.pth` files execute at
|
|
21
|
+
* interpreter startup from the venv's own site-packages, which survives
|
|
22
|
+
* the re-exec. The PYTHONPATH `sitecustomize.py` is retained only as a
|
|
23
|
+
* belt-and-suspenders for the pre-venv bootstrap phase (first build, no
|
|
24
|
+
* `_virtualenvs` on disk yet).
|
|
25
|
+
*
|
|
26
|
+
* The guard itself covers the whole crash family:
|
|
27
|
+
* - wraps `psutil.virtual_memory` / `swap_memory` / `cpu_percent` /
|
|
28
|
+
* `cpu_times` / `disk_io_counters` module-wide, so every call site —
|
|
29
|
+
* including the direct `psutil.virtual_memory()` in
|
|
30
|
+
* `mozbuild.base._run_make` — degrades to a `UserWarning` and a zeroed
|
|
31
|
+
* reading instead of aborting;
|
|
32
|
+
* - guards `SystemResourceMonitor` CONSTRUCTION via an import hook:
|
|
33
|
+
* `poll_interval` is pre-populated before the real `__init__` runs, a
|
|
34
|
+
* failing `__init__` marks the instance degraded instead of raising,
|
|
35
|
+
* and every monitor method no-ops on a degraded instance — so a
|
|
36
|
+
* partially-constructed monitor can never surface the
|
|
37
|
+
* `AttributeError: poll_interval` variant.
|
|
38
|
+
*/
|
|
39
|
+
import { readdir } from 'node:fs/promises';
|
|
40
|
+
import { homedir, tmpdir } from 'node:os';
|
|
41
|
+
import { delimiter, join } from 'node:path';
|
|
42
|
+
import { toError } from '../utils/errors.js';
|
|
43
|
+
import { ensureDir, pathExists, writeText } from '../utils/fs.js';
|
|
44
|
+
import { verbose } from '../utils/logger.js';
|
|
45
|
+
/** Directory name (under the OS temp dir) holding the PYTHONPATH fallback. */
|
|
46
|
+
const RESOURCE_SHIM_DIRNAME = 'fireforge-mach-resource-shim';
|
|
47
|
+
/** Basename (sans extension) of the in-venv guard module. */
|
|
48
|
+
const GUARD_MODULE_NAME = 'fireforge_mach_guard';
|
|
49
|
+
/**
|
|
50
|
+
* Python guard body, importable both as the in-venv `fireforge_mach_guard`
|
|
51
|
+
* module (loaded by the sibling `.pth` at interpreter startup) and as the
|
|
52
|
+
* PYTHONPATH `sitecustomize` fallback. Defensive throughout: a missing or
|
|
53
|
+
* broken psutil/mozsystemmonitor leaves mach untouched, and every wrapper
|
|
54
|
+
* only intercepts the failure path.
|
|
55
|
+
*/
|
|
56
|
+
const GUARD_PYTHON_SOURCE = `# Generated by FireForge - do not edit.
|
|
57
|
+
# Degrades the broken host resource monitor family (psutil vs Darwin) from
|
|
58
|
+
# fatal startup errors into non-fatal warnings, so mach builds and tests
|
|
59
|
+
# proceed. Installed into the mach virtualenv site-packages via a .pth file
|
|
60
|
+
# (survives mach's venv re-exec, which drops PYTHONPATH).
|
|
61
|
+
import builtins
|
|
62
|
+
import sys
|
|
63
|
+
import warnings
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _fireforge_degraded_notice(exc):
|
|
67
|
+
warnings.warn(
|
|
68
|
+
"FireForge: host resource monitor degraded (%s); continuing without resource monitoring." % exc
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _DegradedReading(object):
|
|
73
|
+
total = available = used = free = active = inactive = wired = 0
|
|
74
|
+
percent = 0.0
|
|
75
|
+
|
|
76
|
+
def __getattr__(self, _name):
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _guard_psutil():
|
|
81
|
+
try:
|
|
82
|
+
import psutil
|
|
83
|
+
except Exception:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
def _wrap(orig):
|
|
87
|
+
def wrapper(*args, **kwargs):
|
|
88
|
+
try:
|
|
89
|
+
return orig(*args, **kwargs)
|
|
90
|
+
except Exception as exc: # noqa: BLE001
|
|
91
|
+
_fireforge_degraded_notice(exc)
|
|
92
|
+
return _DegradedReading()
|
|
93
|
+
|
|
94
|
+
return wrapper
|
|
95
|
+
|
|
96
|
+
for _name in ("virtual_memory", "swap_memory", "cpu_percent", "cpu_times", "disk_io_counters"):
|
|
97
|
+
_orig = getattr(psutil, _name, None)
|
|
98
|
+
if _orig is not None and not getattr(_orig, "_fireforge_guarded", False):
|
|
99
|
+
_wrapped = _wrap(_orig)
|
|
100
|
+
_wrapped._fireforge_guarded = True
|
|
101
|
+
setattr(psutil, _name, _wrapped)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_MONITOR_METHOD_NAMES = (
|
|
105
|
+
"start",
|
|
106
|
+
"stop",
|
|
107
|
+
"record_event",
|
|
108
|
+
"record_marker",
|
|
109
|
+
"begin_phase",
|
|
110
|
+
"finish_phase",
|
|
111
|
+
"aggregate_cpu_percent",
|
|
112
|
+
"aggregate_cpu_times",
|
|
113
|
+
"aggregate_io",
|
|
114
|
+
"min_memory_available",
|
|
115
|
+
"as_dict",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _patch_monitor_class(cls):
|
|
120
|
+
if cls is None or getattr(cls, "_fireforge_guarded", False):
|
|
121
|
+
return
|
|
122
|
+
orig_init = cls.__init__
|
|
123
|
+
|
|
124
|
+
def guarded_init(self, *args, **kwargs):
|
|
125
|
+
# Pre-populate the attributes a partially-constructed instance is
|
|
126
|
+
# later polled for, so a failing __init__ can never surface
|
|
127
|
+
# AttributeError: poll_interval.
|
|
128
|
+
self.poll_interval = kwargs.get("poll_interval") or 1.0
|
|
129
|
+
self._fireforge_degraded = False
|
|
130
|
+
try:
|
|
131
|
+
orig_init(self, *args, **kwargs)
|
|
132
|
+
except Exception as exc: # noqa: BLE001
|
|
133
|
+
self._fireforge_degraded = True
|
|
134
|
+
_fireforge_degraded_notice(exc)
|
|
135
|
+
|
|
136
|
+
cls.__init__ = guarded_init
|
|
137
|
+
|
|
138
|
+
def _wrap_method(orig):
|
|
139
|
+
def wrapper(self, *args, **kwargs):
|
|
140
|
+
if getattr(self, "_fireforge_degraded", False):
|
|
141
|
+
return None
|
|
142
|
+
try:
|
|
143
|
+
return orig(self, *args, **kwargs)
|
|
144
|
+
except Exception as exc: # noqa: BLE001
|
|
145
|
+
self._fireforge_degraded = True
|
|
146
|
+
_fireforge_degraded_notice(exc)
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
return wrapper
|
|
150
|
+
|
|
151
|
+
for _name in _MONITOR_METHOD_NAMES:
|
|
152
|
+
_orig = getattr(cls, _name, None)
|
|
153
|
+
if _orig is not None:
|
|
154
|
+
setattr(cls, _name, _wrap_method(_orig))
|
|
155
|
+
cls._fireforge_guarded = True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
_MONITOR_MODULES = ("mozsystemmonitor.resourcemonitor", "mozbuild.resources")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _patch_loaded_monitor_modules():
|
|
162
|
+
for _mod_name in _MONITOR_MODULES:
|
|
163
|
+
_mod = sys.modules.get(_mod_name)
|
|
164
|
+
if _mod is not None:
|
|
165
|
+
try:
|
|
166
|
+
_patch_monitor_class(getattr(_mod, "SystemResourceMonitor", None))
|
|
167
|
+
except Exception: # noqa: BLE001
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _install_import_hook():
|
|
172
|
+
_orig_import = builtins.__import__
|
|
173
|
+
if getattr(_orig_import, "_fireforge_guarded", False):
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
def _guarding_import(name, *args, **kwargs):
|
|
177
|
+
_module = _orig_import(name, *args, **kwargs)
|
|
178
|
+
try:
|
|
179
|
+
_patch_loaded_monitor_modules()
|
|
180
|
+
except Exception: # noqa: BLE001
|
|
181
|
+
pass
|
|
182
|
+
return _module
|
|
183
|
+
|
|
184
|
+
_guarding_import._fireforge_guarded = True
|
|
185
|
+
builtins.__import__ = _guarding_import
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
_guard_psutil()
|
|
190
|
+
_patch_loaded_monitor_modules()
|
|
191
|
+
_install_import_hook()
|
|
192
|
+
except Exception: # noqa: BLE001
|
|
193
|
+
pass
|
|
194
|
+
`;
|
|
195
|
+
/** Lists subdirectory names of `dir`, tolerating a missing directory. */
|
|
196
|
+
async function listSubdirs(dir) {
|
|
197
|
+
try {
|
|
198
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
199
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Discovers `site-packages` directories of the mach virtualenvs under one
|
|
207
|
+
* `_virtualenvs` root: `<root>/<venv>/lib/python<N.M>/site-packages`.
|
|
208
|
+
*/
|
|
209
|
+
async function discoverVenvSitePackages(virtualenvsDir) {
|
|
210
|
+
const found = [];
|
|
211
|
+
for (const venvName of await listSubdirs(virtualenvsDir)) {
|
|
212
|
+
const libDir = join(virtualenvsDir, venvName, 'lib');
|
|
213
|
+
for (const pythonName of await listSubdirs(libDir)) {
|
|
214
|
+
if (!pythonName.startsWith('python'))
|
|
215
|
+
continue;
|
|
216
|
+
const sitePackages = join(libDir, pythonName, 'site-packages');
|
|
217
|
+
if (await pathExists(sitePackages)) {
|
|
218
|
+
found.push(sitePackages);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return found;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Discovers every mach virtualenv site-packages dir relevant to an engine
|
|
226
|
+
* checkout: the objdir venvs (under `<engineDir>/obj-<x>/_virtualenvs`)
|
|
227
|
+
* and the mach state-dir venvs (under `$MOZBUILD_STATE_PATH` or
|
|
228
|
+
* `~/.mozbuild`, in `srcdirs/<hash>/_virtualenvs`, where mach keeps the
|
|
229
|
+
* `mach` command venv itself).
|
|
230
|
+
*/
|
|
231
|
+
async function discoverMachSitePackages(engineDir) {
|
|
232
|
+
const found = [];
|
|
233
|
+
for (const name of await listSubdirs(engineDir)) {
|
|
234
|
+
if (!name.startsWith('obj-'))
|
|
235
|
+
continue;
|
|
236
|
+
found.push(...(await discoverVenvSitePackages(join(engineDir, name, '_virtualenvs'))));
|
|
237
|
+
}
|
|
238
|
+
const stateDir = process.env['MOZBUILD_STATE_PATH'] ?? join(homedir(), '.mozbuild');
|
|
239
|
+
for (const srcdirName of await listSubdirs(join(stateDir, 'srcdirs'))) {
|
|
240
|
+
found.push(...(await discoverVenvSitePackages(join(stateDir, 'srcdirs', srcdirName, '_virtualenvs'))));
|
|
241
|
+
}
|
|
242
|
+
return found;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Writes the PYTHONPATH `sitecustomize.py` fallback into a stable
|
|
246
|
+
* FireForge-owned temp directory and returns that directory. Idempotent —
|
|
247
|
+
* overwriting each call keeps it in sync with this source across upgrades.
|
|
248
|
+
*/
|
|
249
|
+
async function ensureSitecustomizeFallback() {
|
|
250
|
+
const dir = join(tmpdir(), RESOURCE_SHIM_DIRNAME);
|
|
251
|
+
await ensureDir(dir);
|
|
252
|
+
await writeText(join(dir, 'sitecustomize.py'), GUARD_PYTHON_SOURCE);
|
|
253
|
+
return dir;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Builds the `env` overlay (merged over `process.env` by the exec layer)
|
|
257
|
+
* that prepends the fallback shim directory to `PYTHONPATH` without
|
|
258
|
+
* clobbering an existing `PYTHONPATH`.
|
|
259
|
+
*/
|
|
260
|
+
function machResourceShimEnv(shimDir, baseEnv = process.env) {
|
|
261
|
+
const existing = baseEnv['PYTHONPATH'];
|
|
262
|
+
return { PYTHONPATH: existing ? `${shimDir}${delimiter}${existing}` : shimDir };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Installs the resource-monitor degrade guard for a mach dispatch:
|
|
266
|
+
* `fireforge_mach_guard.pth` + `fireforge_mach_guard.py` into every
|
|
267
|
+
* discovered mach virtualenv site-packages (survives mach's venv re-exec),
|
|
268
|
+
* plus the PYTHONPATH sitecustomize fallback for the pre-venv bootstrap
|
|
269
|
+
* phase. Idempotent and re-run before EVERY protected dispatch (including
|
|
270
|
+
* each retry) so a venv created by a crashed first attempt is guarded on
|
|
271
|
+
* the next one instead of re-dying on the same wedged state.
|
|
272
|
+
*/
|
|
273
|
+
export async function installMachResourceGuard(engineDir) {
|
|
274
|
+
const sitePackagesDirs = await discoverMachSitePackages(engineDir);
|
|
275
|
+
for (const sitePackages of sitePackagesDirs) {
|
|
276
|
+
try {
|
|
277
|
+
await writeText(join(sitePackages, `${GUARD_MODULE_NAME}.py`), GUARD_PYTHON_SOURCE);
|
|
278
|
+
await writeText(join(sitePackages, `${GUARD_MODULE_NAME}.pth`), `import ${GUARD_MODULE_NAME}\n`);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
// A read-only or vanished venv must not block the dispatch; the
|
|
282
|
+
// PYTHONPATH fallback still applies and other venvs may have taken
|
|
283
|
+
// the guard.
|
|
284
|
+
verbose(`Could not install mach resource guard into ${sitePackages}: ${toError(error).message}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const env = machResourceShimEnv(await ensureSitecustomizeFallback());
|
|
288
|
+
return { env, sitePackagesDirs };
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=mach-resource-shim.js.map
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SmokeLineCallback, type SmokeRunResult } from '../utils/process.js';
|
|
2
|
+
import { type HarnessCrashSignature } from './test-harness-crash.js';
|
|
2
3
|
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
|
|
3
4
|
export { generateMozconfig } from './mach-mozconfig.js';
|
|
4
5
|
export { ensurePython, resetResolvedPython } from './mach-python.js';
|
|
@@ -57,12 +58,52 @@ export declare function bootstrap(engineDir: string): Promise<number>;
|
|
|
57
58
|
* @returns Captured output and exit code
|
|
58
59
|
*/
|
|
59
60
|
export declare function bootstrapWithOutput(engineDir: string): Promise<MachCommandResult>;
|
|
61
|
+
/** Which mach build entry a protected dispatch runs. */
|
|
62
|
+
export type ProtectedBuildKind = 'full' | 'faster';
|
|
63
|
+
/** Options for {@link runProtectedMachBuild}. */
|
|
64
|
+
export interface ProtectedMachBuildOptions {
|
|
65
|
+
/** Parallel jobs for the full build (ignored for `faster`). */
|
|
66
|
+
jobs?: number | undefined;
|
|
67
|
+
/** Recognized-crash retry budget (0 disables retries). */
|
|
68
|
+
retries?: number | undefined;
|
|
69
|
+
/** Called before each retry with the detected crash signature. */
|
|
70
|
+
onRetry?: (signature: HarnessCrashSignature, nextAttempt: number, maxAttempts: number) => void;
|
|
71
|
+
}
|
|
72
|
+
/** Captured result of a protected (guarded, retried) mach build dispatch. */
|
|
73
|
+
export interface ProtectedMachBuildResult extends MachCommandResult {
|
|
74
|
+
/** How many mach processes were spawned (1 = no retry needed). */
|
|
75
|
+
attempts: number;
|
|
76
|
+
/** Crash signature of the final failed attempt, when recognized. */
|
|
77
|
+
crashSignature?: HarnessCrashSignature;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The single protected path every FireForge mach build dispatch routes
|
|
81
|
+
* through (field report: `build --ui` and the pre-test `--build` step died
|
|
82
|
+
* on the resource-monitor crash family while plain `build` survived,
|
|
83
|
+
* because only some entries were protected and retry budgets differed):
|
|
84
|
+
*
|
|
85
|
+
* 1. installs the resource-monitor degrade guard IN the mach virtualenvs
|
|
86
|
+
* (plus the PYTHONPATH fallback) — re-installed before every attempt,
|
|
87
|
+
* so a venv materialized by a crashed first attempt is guarded on the
|
|
88
|
+
* next one instead of every retry dying on the same wedged state;
|
|
89
|
+
* 2. spawns a fresh mach process per attempt;
|
|
90
|
+
* 3. retries ONLY the recognized harness-crash family (resource monitor /
|
|
91
|
+
* psutil startup tracebacks) up to the uniform budget — an ordinary
|
|
92
|
+
* compile error is never retried.
|
|
93
|
+
*
|
|
94
|
+
* The protected path never runs `mach configure`, never clobbers, and
|
|
95
|
+
* never widens the requested build kind — a `faster` dispatch retries as
|
|
96
|
+
* `mach build faster`, so it cannot invalidate more of the objdir than the
|
|
97
|
+
* command the operator asked for.
|
|
98
|
+
*/
|
|
99
|
+
export declare function runProtectedMachBuild(kind: ProtectedBuildKind, engineDir: string, options?: ProtectedMachBuildOptions): Promise<ProtectedMachBuildResult>;
|
|
60
100
|
/**
|
|
61
|
-
* Runs a full mach build
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
101
|
+
* Runs a full mach build through the protected dispatch path (resource
|
|
102
|
+
* guard + recognized-crash retries). On a non-zero exit, any matched error
|
|
103
|
+
* hints are surfaced on top of the raw mach output so operators get an
|
|
104
|
+
* actionable nudge alongside the cryptic mozbuild traceback. Returns the
|
|
105
|
+
* captured result so the caller (e.g. `fireforge build`) can inspect the
|
|
106
|
+
* tail for post-build diagnostics that mach prints AFTER "Your build was
|
|
66
107
|
* successful!" — notably the stale `config.status is out of date`
|
|
67
108
|
* notice that mach emits when a tool-managed edit landed on
|
|
68
109
|
* `moz.configure` before the build.
|
|
@@ -70,15 +111,15 @@ export declare function bootstrapWithOutput(engineDir: string): Promise<MachComm
|
|
|
70
111
|
* @param jobs - Number of parallel jobs (optional)
|
|
71
112
|
* @returns Captured mach result (stdout tail, stderr tail, exit code)
|
|
72
113
|
*/
|
|
73
|
-
export declare function build(engineDir: string, jobs?: number): Promise<
|
|
114
|
+
export declare function build(engineDir: string, jobs?: number): Promise<ProtectedMachBuildResult>;
|
|
74
115
|
/**
|
|
75
|
-
* Runs a fast UI-only build
|
|
76
|
-
*
|
|
77
|
-
*
|
|
116
|
+
* Runs a fast UI-only build through the same protected dispatch path as
|
|
117
|
+
* {@link build}. See {@link build} for why the full captured result is
|
|
118
|
+
* returned rather than just the exit code.
|
|
78
119
|
* @param engineDir - Path to the engine directory
|
|
79
120
|
* @returns Captured mach result
|
|
80
121
|
*/
|
|
81
|
-
export declare function buildUI(engineDir: string): Promise<
|
|
122
|
+
export declare function buildUI(engineDir: string): Promise<ProtectedMachBuildResult>;
|
|
82
123
|
/**
|
|
83
124
|
* Runs an operation while holding a sidecar build lock keyed on the
|
|
84
125
|
* project root. Concurrent `fireforge build` / `fireforge build --ui`
|
package/dist/src/core/mach.js
CHANGED
|
@@ -8,6 +8,8 @@ import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
|
8
8
|
import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
|
|
9
9
|
import { explainMachError } from './mach-error-hints.js';
|
|
10
10
|
import { getPython } from './mach-python.js';
|
|
11
|
+
import { installMachResourceGuard } from './mach-resource-shim.js';
|
|
12
|
+
import { detectHarnessCrashSignature } from './test-harness-crash.js';
|
|
11
13
|
// Re-export sub-modules so existing `from './mach.js'` imports keep working.
|
|
12
14
|
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
|
|
13
15
|
export { generateMozconfig } from './mach-mozconfig.js';
|
|
@@ -139,11 +141,63 @@ function surfaceMachErrorHints(result) {
|
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
|
|
144
|
+
* Uniform recognized-crash retry budget for the protected mach build
|
|
145
|
+
* dispatches (`fireforge build`, `build --ui`, and the pre-test `--build`
|
|
146
|
+
* step). Matches the test harness default so every mach dispatch retries
|
|
147
|
+
* the same crash family the same number of times.
|
|
148
|
+
*/
|
|
149
|
+
const DEFAULT_BUILD_CRASH_RETRIES = 2;
|
|
150
|
+
/**
|
|
151
|
+
* The single protected path every FireForge mach build dispatch routes
|
|
152
|
+
* through (field report: `build --ui` and the pre-test `--build` step died
|
|
153
|
+
* on the resource-monitor crash family while plain `build` survived,
|
|
154
|
+
* because only some entries were protected and retry budgets differed):
|
|
155
|
+
*
|
|
156
|
+
* 1. installs the resource-monitor degrade guard IN the mach virtualenvs
|
|
157
|
+
* (plus the PYTHONPATH fallback) — re-installed before every attempt,
|
|
158
|
+
* so a venv materialized by a crashed first attempt is guarded on the
|
|
159
|
+
* next one instead of every retry dying on the same wedged state;
|
|
160
|
+
* 2. spawns a fresh mach process per attempt;
|
|
161
|
+
* 3. retries ONLY the recognized harness-crash family (resource monitor /
|
|
162
|
+
* psutil startup tracebacks) up to the uniform budget — an ordinary
|
|
163
|
+
* compile error is never retried.
|
|
164
|
+
*
|
|
165
|
+
* The protected path never runs `mach configure`, never clobbers, and
|
|
166
|
+
* never widens the requested build kind — a `faster` dispatch retries as
|
|
167
|
+
* `mach build faster`, so it cannot invalidate more of the objdir than the
|
|
168
|
+
* command the operator asked for.
|
|
169
|
+
*/
|
|
170
|
+
export async function runProtectedMachBuild(kind, engineDir, options = {}) {
|
|
171
|
+
const args = kind === 'faster' ? ['build', 'faster'] : ['build'];
|
|
172
|
+
if (kind === 'full' && options.jobs !== undefined) {
|
|
173
|
+
args.push('-j', String(options.jobs));
|
|
174
|
+
}
|
|
175
|
+
const maxAttempts = Math.max(1, (options.retries ?? DEFAULT_BUILD_CRASH_RETRIES) + 1);
|
|
176
|
+
for (let attempt = 1;; attempt += 1) {
|
|
177
|
+
const { env } = await installMachResourceGuard(engineDir);
|
|
178
|
+
const result = await runMachInheritCapture(args, engineDir, { env });
|
|
179
|
+
if (result.exitCode === 0) {
|
|
180
|
+
return { ...result, attempts: attempt };
|
|
181
|
+
}
|
|
182
|
+
const signature = detectHarnessCrashSignature(`${result.stdout}\n${result.stderr}`);
|
|
183
|
+
if (signature && attempt < maxAttempts) {
|
|
184
|
+
options.onRetry?.(signature, attempt + 1, maxAttempts);
|
|
185
|
+
warn(`mach ${kind === 'faster' ? 'build faster' : 'build'} hit a recognized harness crash ` +
|
|
186
|
+
`(${signature.reason}): ${signature.line}\n` +
|
|
187
|
+
`Retrying with a fresh process (attempt ${attempt + 1} of ${maxAttempts})...`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
surfaceMachErrorHints(result);
|
|
191
|
+
return { ...result, attempts: attempt, ...(signature ? { crashSignature: signature } : {}) };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Runs a full mach build through the protected dispatch path (resource
|
|
196
|
+
* guard + recognized-crash retries). On a non-zero exit, any matched error
|
|
197
|
+
* hints are surfaced on top of the raw mach output so operators get an
|
|
198
|
+
* actionable nudge alongside the cryptic mozbuild traceback. Returns the
|
|
199
|
+
* captured result so the caller (e.g. `fireforge build`) can inspect the
|
|
200
|
+
* tail for post-build diagnostics that mach prints AFTER "Your build was
|
|
147
201
|
* successful!" — notably the stale `config.status is out of date`
|
|
148
202
|
* notice that mach emits when a tool-managed edit landed on
|
|
149
203
|
* `moz.configure` before the build.
|
|
@@ -152,29 +206,17 @@ function surfaceMachErrorHints(result) {
|
|
|
152
206
|
* @returns Captured mach result (stdout tail, stderr tail, exit code)
|
|
153
207
|
*/
|
|
154
208
|
export async function build(engineDir, jobs) {
|
|
155
|
-
|
|
156
|
-
if (jobs !== undefined) {
|
|
157
|
-
args.push('-j', String(jobs));
|
|
158
|
-
}
|
|
159
|
-
const result = await runMachInheritCapture(args, engineDir);
|
|
160
|
-
if (result.exitCode !== 0) {
|
|
161
|
-
surfaceMachErrorHints(result);
|
|
162
|
-
}
|
|
163
|
-
return result;
|
|
209
|
+
return runProtectedMachBuild('full', engineDir, { jobs });
|
|
164
210
|
}
|
|
165
211
|
/**
|
|
166
|
-
* Runs a fast UI-only build
|
|
167
|
-
*
|
|
168
|
-
*
|
|
212
|
+
* Runs a fast UI-only build through the same protected dispatch path as
|
|
213
|
+
* {@link build}. See {@link build} for why the full captured result is
|
|
214
|
+
* returned rather than just the exit code.
|
|
169
215
|
* @param engineDir - Path to the engine directory
|
|
170
216
|
* @returns Captured mach result
|
|
171
217
|
*/
|
|
172
218
|
export async function buildUI(engineDir) {
|
|
173
|
-
|
|
174
|
-
if (result.exitCode !== 0) {
|
|
175
|
-
surfaceMachErrorHints(result);
|
|
176
|
-
}
|
|
177
|
-
return result;
|
|
219
|
+
return runProtectedMachBuild('faster', engineDir);
|
|
178
220
|
}
|
|
179
221
|
/**
|
|
180
222
|
* Runs an operation while holding a sidecar build lock keyed on the
|
|
@@ -318,7 +360,10 @@ export async function test(engineDir, testPaths = [], args = []) {
|
|
|
318
360
|
* `fireforge test --perf-samples` to publish the artifact-path contract.
|
|
319
361
|
*/
|
|
320
362
|
export async function testWithOutput(engineDir, testPaths = [], args = [], env) {
|
|
321
|
-
|
|
363
|
+
const guard = await installMachResourceGuard(engineDir);
|
|
364
|
+
return runMachCapture(['test', ...testPaths, ...args], engineDir, {
|
|
365
|
+
env: { ...guard.env, ...env },
|
|
366
|
+
});
|
|
322
367
|
}
|
|
323
368
|
/**
|
|
324
369
|
* Runs `mach xpcshell-test` (the suite-specific xpcshell command) while
|
|
@@ -331,7 +376,10 @@ export async function testWithOutput(engineDir, testPaths = [], args = [], env)
|
|
|
331
376
|
* the dispatch path.
|
|
332
377
|
*/
|
|
333
378
|
export async function xpcshellTestWithOutput(engineDir, testPaths = [], args = [], env) {
|
|
334
|
-
|
|
379
|
+
const guard = await installMachResourceGuard(engineDir);
|
|
380
|
+
return runMachCapture(['xpcshell-test', ...testPaths, ...args], engineDir, {
|
|
381
|
+
env: { ...guard.env, ...env },
|
|
382
|
+
});
|
|
335
383
|
}
|
|
336
384
|
/**
|
|
337
385
|
* Runs `mach mochitest` (covers browser-chrome / mochitest flavors) while
|
|
@@ -339,6 +387,9 @@ export async function xpcshellTestWithOutput(engineDir, testPaths = [], args = [
|
|
|
339
387
|
* for non-xpcshell single-suite runs — see {@link xpcshellTestWithOutput}.
|
|
340
388
|
*/
|
|
341
389
|
export async function mochitestWithOutput(engineDir, testPaths = [], args = [], env) {
|
|
342
|
-
|
|
390
|
+
const guard = await installMachResourceGuard(engineDir);
|
|
391
|
+
return runMachCapture(['mochitest', ...testPaths, ...args], engineDir, {
|
|
392
|
+
env: { ...guard.env, ...env },
|
|
393
|
+
});
|
|
343
394
|
}
|
|
344
395
|
//# sourceMappingURL=mach.js.map
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { JarMnToken, MozBuildToken } from './manifest-tokenizers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ordering comparator matching mozbuild's `StrictOrderingOnAppendList`
|
|
4
|
+
* (`mozbuild.util.UnsortedError`): entries are compared **case-insensitively**,
|
|
5
|
+
* so `HominisAppearanceController` (`appe`) sorts before
|
|
6
|
+
* `HominisAppMenuIntegration` (`appm`) even though a raw byte comparison
|
|
7
|
+
* places the uppercase `M` (0x4D) before the lowercase `e` (0x65).
|
|
8
|
+
*
|
|
9
|
+
* A naive `a > b` insertion landed the new entry in byte order, which
|
|
10
|
+
* `mach configure` then rejected with `UnsortedError`, aborting the build.
|
|
11
|
+
* Ties on the lower-cased key fall back to byte order so the comparison is
|
|
12
|
+
* total and stable.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mozbuildSortCompare(a: string, b: string): number;
|
|
2
15
|
/**
|
|
3
16
|
* Inserts a line into an array of lines in alphabetical order within a
|
|
4
17
|
* specified range. The comparison key is extracted from each line.
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordering comparator matching mozbuild's `StrictOrderingOnAppendList`
|
|
3
|
+
* (`mozbuild.util.UnsortedError`): entries are compared **case-insensitively**,
|
|
4
|
+
* so `HominisAppearanceController` (`appe`) sorts before
|
|
5
|
+
* `HominisAppMenuIntegration` (`appm`) even though a raw byte comparison
|
|
6
|
+
* places the uppercase `M` (0x4D) before the lowercase `e` (0x65).
|
|
7
|
+
*
|
|
8
|
+
* A naive `a > b` insertion landed the new entry in byte order, which
|
|
9
|
+
* `mach configure` then rejected with `UnsortedError`, aborting the build.
|
|
10
|
+
* Ties on the lower-cased key fall back to byte order so the comparison is
|
|
11
|
+
* total and stable.
|
|
12
|
+
*/
|
|
13
|
+
export function mozbuildSortCompare(a, b) {
|
|
14
|
+
const la = a.toLowerCase();
|
|
15
|
+
const lb = b.toLowerCase();
|
|
16
|
+
if (la < lb)
|
|
17
|
+
return -1;
|
|
18
|
+
if (la > lb)
|
|
19
|
+
return 1;
|
|
20
|
+
if (a < b)
|
|
21
|
+
return -1;
|
|
22
|
+
if (a > b)
|
|
23
|
+
return 1;
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
1
26
|
/**
|
|
2
27
|
* Inserts a line into an array of lines in alphabetical order within a
|
|
3
28
|
* specified range. The comparison key is extracted from each line.
|
|
@@ -16,7 +41,7 @@ export function findAlphabeticalPosition(lines, startLine, endLine, newKey, extr
|
|
|
16
41
|
existingKeys.push(key);
|
|
17
42
|
}
|
|
18
43
|
}
|
|
19
|
-
const isSorted = existingKeys.every((k, idx) => idx === 0 || k
|
|
44
|
+
const isSorted = existingKeys.every((k, idx) => idx === 0 || mozbuildSortCompare(k, existingKeys[idx - 1] ?? '') >= 0);
|
|
20
45
|
if (!isSorted) {
|
|
21
46
|
return { insertIndex: endLine, previousEntry: undefined };
|
|
22
47
|
}
|
|
@@ -29,7 +54,7 @@ export function findAlphabeticalPosition(lines, startLine, endLine, newKey, extr
|
|
|
29
54
|
const key = extractKey(line);
|
|
30
55
|
if (key === undefined)
|
|
31
56
|
continue;
|
|
32
|
-
if (key >
|
|
57
|
+
if (mozbuildSortCompare(key, newKey) > 0) {
|
|
33
58
|
insertIndex = i;
|
|
34
59
|
break;
|
|
35
60
|
}
|
|
@@ -61,7 +86,7 @@ export function findAlphabeticalTokenPosition(tokens, sectionTargetPattern, newK
|
|
|
61
86
|
continue;
|
|
62
87
|
const match = sectionTargetPattern.exec(entry.parsed.target);
|
|
63
88
|
const key = match?.[1] ?? entry.parsed.target;
|
|
64
|
-
if (key >
|
|
89
|
+
if (mozbuildSortCompare(key, newKey) > 0) {
|
|
65
90
|
insertLineIndex = entry.lineIndex;
|
|
66
91
|
break;
|
|
67
92
|
}
|
|
@@ -84,7 +109,7 @@ export function findAlphabeticalMozBuildPosition(tokens, newKey) {
|
|
|
84
109
|
let previousEntry;
|
|
85
110
|
for (const item of items) {
|
|
86
111
|
const key = item.parsed?.value ?? '';
|
|
87
|
-
if (key >
|
|
112
|
+
if (mozbuildSortCompare(key, newKey) > 0) {
|
|
88
113
|
insertLineIndex = item.lineIndex;
|
|
89
114
|
break;
|
|
90
115
|
}
|
|
@@ -10,8 +10,14 @@ export interface PatternRule {
|
|
|
10
10
|
/** Extract arguments from the regex match */
|
|
11
11
|
extractArgs: (match: RegExpMatchArray) => string[];
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Returns manifest registration rules for the supported engine file
|
|
15
|
+
* patterns. `createManifest` (from `register --create-manifest`) lets the
|
|
16
|
+
* module and xpcshell-test rules scaffold a missing manifest (directory
|
|
17
|
+
* moz.build / xpcshell.toml) and wire the parent chain instead of failing
|
|
18
|
+
* with "Manifest not found".
|
|
19
|
+
*/
|
|
20
|
+
export declare function getRules(binaryName: string, createManifest?: boolean): PatternRule[];
|
|
15
21
|
/**
|
|
16
22
|
* Checks if a file path matches any known registrable pattern.
|
|
17
23
|
* @param filePath - Path relative to engine/
|
|
@@ -34,6 +40,10 @@ export declare function isFileRegistered(root: string, filePath: string): Promis
|
|
|
34
40
|
* @param filePath - Path relative to engine/
|
|
35
41
|
* @param dryRun - If true, return what would be done without writing
|
|
36
42
|
* @param after - Optional substring to place entry after (instead of alphabetical)
|
|
43
|
+
* @param options - `createManifest` scaffolds a missing manifest (directory
|
|
44
|
+
* moz.build / xpcshell.toml) and wires the parent chain instead of failing
|
|
37
45
|
* @returns Registration result
|
|
38
46
|
*/
|
|
39
|
-
export declare function registerFile(root: string, filePath: string, dryRun?: boolean, after?: string
|
|
47
|
+
export declare function registerFile(root: string, filePath: string, dryRun?: boolean, after?: string, options?: {
|
|
48
|
+
createManifest?: boolean;
|
|
49
|
+
}): Promise<RegisterResult>;
|