@kungfu-tech/buildchain 2.4.3-alpha.0 → 2.4.3
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.
|
@@ -57,7 +57,10 @@ first, restores the local working copies, and only then decides whether to
|
|
|
57
57
|
publish, repair, or finalize.
|
|
58
58
|
|
|
59
59
|
Every meaningful state transition is written to the durable ref before public
|
|
60
|
-
release refs move.
|
|
60
|
+
release refs move. Release-state GitHub API reads and writes use retry/backoff
|
|
61
|
+
for transient service failures such as HTTP 5xx responses, connection resets,
|
|
62
|
+
timeouts, and "other side closed" socket failures. If the durable write still
|
|
63
|
+
cannot be persisted after retries, the action fails closed.
|
|
61
64
|
|
|
62
65
|
Durable release-state refs reserve their exact version even when the public exact
|
|
63
66
|
tag was never created. If a later machine run sees a failed or repair-required
|
|
@@ -296,6 +299,9 @@ the transaction `release_sha`. Exact tags are accepted when they already point
|
|
|
296
299
|
at the transaction release/material SHA or the finalized channel head. Floating
|
|
297
300
|
channel tags and dev/alpha refs are then retried idempotently, and the
|
|
298
301
|
transaction is marked `complete` only after those public refs are consistent.
|
|
302
|
+
Writing `complete` clears any stale `failure` value from earlier attempts, so
|
|
303
|
+
the durable `state.json` represents the successful final state instead of the
|
|
304
|
+
last transient error seen before a rerun.
|
|
299
305
|
An exact tag at an unrelated SHA is still a material conflict and blocks
|
|
300
306
|
recovery.
|
|
301
307
|
|
|
@@ -158,18 +158,24 @@ writeDiagnosticsArtifact(".buildchain/artifacts/diagnostics.json", {
|
|
|
158
158
|
`collectCacheDiagnostics()` includes package-manager/workspace context, selected
|
|
159
159
|
cache directory stats, and compiler-cache stats from `ccache --show-stats
|
|
160
160
|
--json` plus `sccache --show-stats --stats-format json` when those tools are
|
|
161
|
-
present.
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
present. If a ccache build does not support JSON stats, Buildchain falls back to
|
|
162
|
+
plain `ccache --show-stats` and parses the text counters. Missing cache tools
|
|
163
|
+
are recorded as unavailable instead of failing the diagnostics artifact. Call
|
|
164
|
+
`collectCompilerCacheDiagnostics()` directly when a consumer script only needs
|
|
165
|
+
compiler cache data. Native diagnostics also expose `compilerCaches` and
|
|
166
|
+
`nativeCacheDirs` as top-level fields in each diagnostics artifact and aggregate
|
|
167
|
+
summary, so reviewers do not have to dig through nested cache sections first.
|
|
164
168
|
|
|
165
169
|
Process samples are intentionally summarized before they become long-lived
|
|
166
170
|
artifacts. The summary records requested parallelism, the source of that value
|
|
167
|
-
(`command`, `
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
`build-tool`, and `cache`.
|
|
171
|
+
(`command`, `process-tree`, `env:MAKEFLAGS`,
|
|
172
|
+
`env:CMAKE_BUILD_PARALLEL_LEVEL`, or `explicit`), observed active process
|
|
173
|
+
concurrency, elapsed sample time, total sampled CPU, and conservative command
|
|
174
|
+
categories such as `compiler`, `archive`, `linker`, `build-tool`, and `cache`.
|
|
175
|
+
The sampler detects common `make -j N`, `ninja -j N`, CMake/MSBuild/Xcode job
|
|
176
|
+
flags, and MAKEFLAGS. This lets native projects distinguish "we asked for
|
|
171
177
|
`make -j20`" from "the build graph only kept two active compiler or archive
|
|
172
|
-
children busy during the sampled window" without storing
|
|
178
|
+
children busy during the sampled window" without storing environment dumps.
|
|
173
179
|
|
|
174
180
|
Native repositories can opt into a reusable diagnostics profile in
|
|
175
181
|
`buildchain.toml`:
|
|
@@ -304,12 +310,15 @@ buildchain sample process-tree \
|
|
|
304
310
|
make -j20
|
|
305
311
|
```
|
|
306
312
|
|
|
307
|
-
The JSONL sample file stores timestamped process-tree snapshots with
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
+
The JSONL sample file stores timestamped process-tree snapshots with full
|
|
314
|
+
redacted command lines, command basenames, CPU percentages when available,
|
|
315
|
+
elapsed time, and requested parallelism context. Unix and macOS snapshots read
|
|
316
|
+
the full `ps args` field instead of truncated `comm` names. Windows snapshots
|
|
317
|
+
read `Win32_Process` command lines through PowerShell so the sampler no longer
|
|
318
|
+
returns an empty process set on Windows runners. The summary file records
|
|
319
|
+
observed concurrency, total sampled CPU, command categories, and top command
|
|
320
|
+
basenames. This is intended for diagnosing low-utilization tails such as
|
|
321
|
+
archive/link phases without logging full environment dumps.
|
|
313
322
|
|
|
314
323
|
The lifecycle observability summary is stage-wide, not just final-step timing:
|
|
315
324
|
when install and build write to the same Buildchain log, the final platform
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.4.3
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -497,26 +497,70 @@ function parseJsonDiagnostics(value = "") {
|
|
|
497
497
|
}
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
500
|
+
function parseTextStats(value = "") {
|
|
501
|
+
const stats = {};
|
|
502
|
+
for (const line of String(value || "").split(/\r?\n/)) {
|
|
503
|
+
const trimmed = line.trim();
|
|
504
|
+
if (!trimmed) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const match = trimmed.match(/^(.+?)(?:\s{2,}|\t+|:\s+)([-+]?\d+(?:\.\d+)?)(?:\s|$)/);
|
|
508
|
+
if (!match) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const key = match[1]
|
|
512
|
+
.trim()
|
|
513
|
+
.toLowerCase()
|
|
514
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
515
|
+
.replace(/^_+|_+$/g, "");
|
|
516
|
+
if (!key) {
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const number = Number(match[2]);
|
|
520
|
+
stats[key] = Number.isFinite(number) ? number : match[2];
|
|
519
521
|
}
|
|
522
|
+
return stats;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function collectCompilerCacheTool({ command, attempts, cwd, runCommand }) {
|
|
526
|
+
let lastError;
|
|
527
|
+
for (const attempt of attempts) {
|
|
528
|
+
const args = attempt.args || [];
|
|
529
|
+
const expectedFormat = attempt.format || "";
|
|
530
|
+
try {
|
|
531
|
+
const output = runCommand(command, args, { cwd, timeoutMs: 5000 });
|
|
532
|
+
const text = String(output || "").trim();
|
|
533
|
+
const jsonStats = parseJsonDiagnostics(text);
|
|
534
|
+
if (jsonStats) {
|
|
535
|
+
return {
|
|
536
|
+
available: true,
|
|
537
|
+
command,
|
|
538
|
+
args,
|
|
539
|
+
format: "json",
|
|
540
|
+
stats: jsonStats,
|
|
541
|
+
rawBytes: Buffer.byteLength(text, "utf8"),
|
|
542
|
+
parseError: "",
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
const textStats = parseTextStats(text);
|
|
546
|
+
return {
|
|
547
|
+
available: true,
|
|
548
|
+
command,
|
|
549
|
+
args,
|
|
550
|
+
format: "text",
|
|
551
|
+
stats: textStats,
|
|
552
|
+
rawBytes: Buffer.byteLength(text, "utf8"),
|
|
553
|
+
parseError: Object.keys(textStats).length ? "" : `${expectedFormat || "stats"} output was not parseable`,
|
|
554
|
+
};
|
|
555
|
+
} catch (error) {
|
|
556
|
+
lastError = error;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
available: false,
|
|
561
|
+
command,
|
|
562
|
+
error: lastError?.code || lastError?.message || "stats command failed",
|
|
563
|
+
};
|
|
520
564
|
}
|
|
521
565
|
|
|
522
566
|
export function collectCompilerCacheDiagnostics({
|
|
@@ -527,13 +571,18 @@ export function collectCompilerCacheDiagnostics({
|
|
|
527
571
|
return {
|
|
528
572
|
ccache: collectCompilerCacheTool({
|
|
529
573
|
command: "ccache",
|
|
530
|
-
|
|
574
|
+
attempts: [
|
|
575
|
+
{ args: ["--show-stats", "--json"], format: "json" },
|
|
576
|
+
{ args: ["--show-stats"], format: "text" },
|
|
577
|
+
],
|
|
531
578
|
cwd: resolvedCwd,
|
|
532
579
|
runCommand,
|
|
533
580
|
}),
|
|
534
581
|
sccache: collectCompilerCacheTool({
|
|
535
582
|
command: "sccache",
|
|
536
|
-
|
|
583
|
+
attempts: [
|
|
584
|
+
{ args: ["--show-stats", "--stats-format", "json"], format: "json" },
|
|
585
|
+
],
|
|
537
586
|
cwd: resolvedCwd,
|
|
538
587
|
runCommand,
|
|
539
588
|
}),
|
|
@@ -578,41 +627,21 @@ export function redactDiagnosticsValue(key, value, pattern = DEFAULT_SECRET_KEY_
|
|
|
578
627
|
return value;
|
|
579
628
|
}
|
|
580
629
|
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
encoding: "utf8",
|
|
591
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
592
|
-
timeout: 5000,
|
|
593
|
-
}).split(/\r?\n/).filter(Boolean);
|
|
594
|
-
} catch {
|
|
595
|
-
return { rootPid: pid, platform: process.platform, processes: [] };
|
|
596
|
-
}
|
|
597
|
-
const rows = lines.map((line) => {
|
|
598
|
-
const match = line.trim().match(/^(\d+)\s+(\d+)\s+([0-9.]+)\s+(\S+)(?:\s+(.*))?$/);
|
|
599
|
-
const args = match?.[5] || "";
|
|
600
|
-
return match
|
|
601
|
-
? {
|
|
602
|
-
pid: match[1],
|
|
603
|
-
ppid: match[2],
|
|
604
|
-
cpu: Number(match[3]),
|
|
605
|
-
command: path.basename(match[4]),
|
|
606
|
-
commandLine: redactCommandLine(args),
|
|
607
|
-
}
|
|
608
|
-
: undefined;
|
|
609
|
-
}).filter(Boolean);
|
|
630
|
+
function processCommandFromLine(commandLine = "", fallback = "") {
|
|
631
|
+
const firstToken = shellishTokens(commandLine)[0] || fallback;
|
|
632
|
+
return String(firstToken || "")
|
|
633
|
+
.replace(/[()]/g, "")
|
|
634
|
+
.split(/[\\/]/)
|
|
635
|
+
.pop();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function normalizeProcessRows({ rootPid, platform, rows = [] }) {
|
|
610
639
|
const byParent = new Map();
|
|
611
640
|
for (const row of rows) {
|
|
612
641
|
byParent.set(row.ppid, [...(byParent.get(row.ppid) || []), row]);
|
|
613
642
|
}
|
|
614
643
|
const seen = new Set();
|
|
615
|
-
const stack = [
|
|
644
|
+
const stack = [String(rootPid)];
|
|
616
645
|
const processes = [];
|
|
617
646
|
while (stack.length) {
|
|
618
647
|
const current = stack.pop();
|
|
@@ -625,11 +654,80 @@ export function collectProcessTreeSnapshot({ rootPid = process.pid, cwd = proces
|
|
|
625
654
|
stack.push(child.pid);
|
|
626
655
|
}
|
|
627
656
|
}
|
|
628
|
-
return { rootPid:
|
|
657
|
+
return { rootPid: String(rootPid), platform, processes };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function collectWindowsProcessRows({ cwd, runCommand }) {
|
|
661
|
+
const script = [
|
|
662
|
+
"$ErrorActionPreference = 'Stop'",
|
|
663
|
+
"Get-CimInstance Win32_Process |",
|
|
664
|
+
"Select-Object ProcessId,ParentProcessId,Name,CommandLine |",
|
|
665
|
+
"ConvertTo-Json -Compress",
|
|
666
|
+
].join("; ");
|
|
667
|
+
const output = runCommand("powershell", ["-NoProfile", "-Command", script], {
|
|
668
|
+
cwd,
|
|
669
|
+
timeoutMs: 5000,
|
|
670
|
+
});
|
|
671
|
+
const parsed = JSON.parse(String(output || "[]"));
|
|
672
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
673
|
+
return entries.map((entry) => {
|
|
674
|
+
const commandLine = redactCommandLine(entry.CommandLine || entry.Name || "");
|
|
675
|
+
return {
|
|
676
|
+
pid: String(entry.ProcessId || ""),
|
|
677
|
+
ppid: String(entry.ParentProcessId || ""),
|
|
678
|
+
cpu: 0,
|
|
679
|
+
command: processCommandFromLine(commandLine, entry.Name || ""),
|
|
680
|
+
commandLine,
|
|
681
|
+
};
|
|
682
|
+
}).filter((entry) => entry.pid && entry.ppid);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export function collectProcessTreeSnapshot({
|
|
686
|
+
rootPid = process.pid,
|
|
687
|
+
cwd = process.cwd(),
|
|
688
|
+
platform = process.platform,
|
|
689
|
+
runCommand = defaultDiagnosticCommandRunner,
|
|
690
|
+
} = {}) {
|
|
691
|
+
const pid = String(rootPid || process.pid);
|
|
692
|
+
if (platform === "win32") {
|
|
693
|
+
try {
|
|
694
|
+
return normalizeProcessRows({
|
|
695
|
+
rootPid: pid,
|
|
696
|
+
platform,
|
|
697
|
+
rows: collectWindowsProcessRows({ cwd, runCommand }),
|
|
698
|
+
});
|
|
699
|
+
} catch {
|
|
700
|
+
return { rootPid: pid, platform, processes: [] };
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
let lines = [];
|
|
704
|
+
try {
|
|
705
|
+
lines = runCommand("ps", ["-axo", "pid=,ppid=,pcpu=,args="], {
|
|
706
|
+
cwd,
|
|
707
|
+
timeoutMs: 5000,
|
|
708
|
+
}).split(/\r?\n/).filter(Boolean);
|
|
709
|
+
} catch {
|
|
710
|
+
return { rootPid: pid, platform, processes: [] };
|
|
711
|
+
}
|
|
712
|
+
const rows = lines.map((line) => {
|
|
713
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)\s+([0-9.]+)\s+(.*)$/);
|
|
714
|
+
const args = match?.[4] || "";
|
|
715
|
+
const commandLine = redactCommandLine(args);
|
|
716
|
+
return match
|
|
717
|
+
? {
|
|
718
|
+
pid: match[1],
|
|
719
|
+
ppid: match[2],
|
|
720
|
+
cpu: Number(match[3]),
|
|
721
|
+
command: processCommandFromLine(commandLine),
|
|
722
|
+
commandLine,
|
|
723
|
+
}
|
|
724
|
+
: undefined;
|
|
725
|
+
}).filter(Boolean);
|
|
726
|
+
return normalizeProcessRows({ rootPid: pid, platform, rows });
|
|
629
727
|
}
|
|
630
728
|
|
|
631
729
|
export function classifyProcessCommand(command = "") {
|
|
632
|
-
const name =
|
|
730
|
+
const name = String(command || "").split(/[\\/]/).pop().toLowerCase();
|
|
633
731
|
if (/^(ccache|sccache)$/.test(name)) {
|
|
634
732
|
return "cache";
|
|
635
733
|
}
|
|
@@ -657,7 +755,12 @@ function firstPositiveInteger(value) {
|
|
|
657
755
|
}
|
|
658
756
|
|
|
659
757
|
function commandName(value = "") {
|
|
660
|
-
return
|
|
758
|
+
return String(value || "")
|
|
759
|
+
.replace(/[()]/g, "")
|
|
760
|
+
.replace(/\.exe$/i, "")
|
|
761
|
+
.split(/[\\/]/)
|
|
762
|
+
.pop()
|
|
763
|
+
.toLowerCase();
|
|
661
764
|
}
|
|
662
765
|
|
|
663
766
|
function detectParallelismFromTokens(tokens = []) {
|
|
@@ -945,6 +1048,8 @@ export function createDiagnosticsArtifact({
|
|
|
945
1048
|
const events = logPath ? readBuildchainLogEvents(logPath) : [];
|
|
946
1049
|
const loadedConfig = loadBuildchainConfig(resolvedCwd);
|
|
947
1050
|
const nativeProfile = getNativeDiagnosticsProfile(loadedConfig);
|
|
1051
|
+
const native = collectNativeDiagnostics({ cwd: resolvedCwd, profile: nativeProfile });
|
|
1052
|
+
const cache = collectCacheDiagnostics({ cwd: resolvedCwd, cacheDirs });
|
|
948
1053
|
return {
|
|
949
1054
|
schemaVersion: 1,
|
|
950
1055
|
contract: BUILDCHAIN_DIAGNOSTICS_CONTRACT,
|
|
@@ -953,8 +1058,10 @@ export function createDiagnosticsArtifact({
|
|
|
953
1058
|
buildchain: collectBuildchainDiagnostics({ cwd: resolvedCwd, artifactPaths }),
|
|
954
1059
|
runner: collectRunnerDiagnostics(),
|
|
955
1060
|
tools: collectToolDiagnostics({ cwd: resolvedCwd }),
|
|
956
|
-
cache
|
|
957
|
-
native
|
|
1061
|
+
cache,
|
|
1062
|
+
native,
|
|
1063
|
+
compilerCaches: native.compilerCaches || cache.compilerCaches || {},
|
|
1064
|
+
nativeCacheDirs: native.cacheDirs || [],
|
|
958
1065
|
git: collectGitDiagnostics({ cwd: resolvedCwd }),
|
|
959
1066
|
lifecycleObservability: lifecycleObservability || summarizeLifecycleObservability({ events, logPath }),
|
|
960
1067
|
process: processSummary || summarizeProcessSamples({ samples: processSamples, requestedParallelism }),
|
|
@@ -1353,6 +1460,12 @@ export function summarizeDiagnosticsArtifacts(inputs = []) {
|
|
|
1353
1460
|
runnerDetails: compactRunnerDetails(entry.runner || {}),
|
|
1354
1461
|
tools: compactToolSummary(entry.tools || {}, entry.native?.tools || {}),
|
|
1355
1462
|
cache: compactCacheSummary(entry.cache || {}, entry.native || {}),
|
|
1463
|
+
compilerCaches: compactCompilerCacheSummary(entry.compilerCaches || entry.native?.compilerCaches || entry.cache?.compilerCaches || {}),
|
|
1464
|
+
nativeCacheDirs: Array.isArray(entry.nativeCacheDirs)
|
|
1465
|
+
? entry.nativeCacheDirs
|
|
1466
|
+
: Array.isArray(entry.native?.cacheDirs)
|
|
1467
|
+
? entry.native.cacheDirs
|
|
1468
|
+
: [],
|
|
1356
1469
|
process: entry.process || {},
|
|
1357
1470
|
diagnosticsContract,
|
|
1358
1471
|
...(diagnosticsManifest ? { diagnosticsManifest } : {}),
|
|
@@ -164,6 +164,9 @@ export function transitionReleaseTransaction(record, nextState, metadata = {}) {
|
|
|
164
164
|
throw new Error(`cannot transition release transaction from ${currentState} to ${nextState}`);
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
+
const nextFailure = nextState === "complete"
|
|
168
|
+
? optionalString(metadata.failure ?? "")
|
|
169
|
+
: optionalString(metadata.failure ?? record.failure);
|
|
167
170
|
return {
|
|
168
171
|
...record,
|
|
169
172
|
previous_state: currentState === nextState ? record.previous_state || "" : currentState,
|
|
@@ -171,7 +174,7 @@ export function transitionReleaseTransaction(record, nextState, metadata = {}) {
|
|
|
171
174
|
actor: optionalString(metadata.actor ?? record.actor),
|
|
172
175
|
run_id: optionalString(metadata.runId ?? record.run_id),
|
|
173
176
|
superseded_by: optionalString(metadata.supersededBy ?? record.superseded_by),
|
|
174
|
-
failure:
|
|
177
|
+
failure: nextFailure,
|
|
175
178
|
updated_at: nowIso(),
|
|
176
179
|
};
|
|
177
180
|
}
|