Skip to content

Commit ee2add9

Browse files
authored
fix(release): restore 2.5.21 cross builds (#1403)
* fix(release): restore 2.5.21 cross builds * style(test): apply rustfmt to release policy * fix(release): bound cold cross-build concurrency * fix(release): serialize cold cross compilation * fix(release): preserve zig cross C flags * test(release): assert structured workflow policy
1 parent 3b3026c commit ee2add9

4 files changed

Lines changed: 209 additions & 25 deletions

File tree

.github/workflows/release-auto.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
paths:
77
- Cargo.toml
88
- pyproject.toml
9+
# An incomplete release must be retried when its release machinery is
10+
# repaired without forcing another public version bump.
11+
- .github/workflows/release-auto.yml
12+
- .github/workflows/template_native_build.yml
913
workflow_dispatch:
1014
inputs:
1115
publish:

.github/workflows/template_native_build.yml

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,6 @@ env:
4141
CARGO_TERM_COLOR: always
4242
RUSTFLAGS: "-D warnings"
4343
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
44-
# Native Windows release builds can be quiet for longer than soldr's
45-
# default 30 minute cargo diagnostic watchdog while rustc/link.exe are
46-
# still active. Match the release-binary step's 45 minute timeout so
47-
# GitHub Actions owns the outer deadline instead of soldr killing the
48-
# build early.
49-
SOLDR_CARGO_WAIT_TIMEOUT_SECS: "2700"
5044
# cc-rs env vars for the xwin lanes. ring's build.rs uses cc-rs;
5145
# without these, cc-rs invokes plain `clang` and the curve25519.c
5246
# compile fails with `error: no such file or directory: '/imsvc'`
@@ -71,6 +65,29 @@ jobs:
7165
name: Build (${{ inputs.target }})
7266
runs-on: ${{ inputs.runner }}
7367
timeout-minutes: 90
68+
env:
69+
# Native Windows release builds can be quiet for longer than soldr's
70+
# default 30 minute cargo diagnostic watchdog while rustc/link.exe are
71+
# still active. Match the release-binary step's 45 minute timeout so
72+
# GitHub Actions owns the outer deadline instead of soldr killing the
73+
# build early.
74+
SOLDR_CARGO_WAIT_TIMEOUT_SECS: "2700"
75+
# Six cold release lanes run concurrently on two-core hosted runners.
76+
# Bound rustc and soldr concurrency so cross-target codegen stays within
77+
# the runner memory limit instead of being killed by the host.
78+
CARGO_BUILD_JOBS: "1"
79+
SOLDR_JOBS: "1"
80+
# cargo-zigbuild clears generic CFLAGS while constructing a cross-target
81+
# environment. Use cc-rs's target-specific variables so the vendored
82+
# mimalloc-pprof build keeps this narrow Zig diagnostic demotion.
83+
CFLAGS_x86_64_unknown_linux_musl: "-Wno-error=date-time"
84+
CFLAGS_aarch64_unknown_linux_musl: "-Wno-error=date-time"
85+
CFLAGS_x86_64_apple_darwin: "-Wno-error=date-time"
86+
CFLAGS_aarch64_apple_darwin: "-Wno-error=date-time"
87+
# Keep every soldr invocation in this reusable job on the same catalogue.
88+
# soldr >= 0.9.5 consumes its multipart v2 assets, including Apple SDKs
89+
# whose legacy direct-LFS URLs are no longer anonymously downloadable.
90+
SOLDR_TOOLCHAIN_ORIGIN: https://zackees.github.io/soldr-toolchain
7491

7592
steps:
7693
- uses: actions/checkout@v6
@@ -93,15 +110,13 @@ jobs:
93110
# v0.7.85/v0.7.87 were broken releases that shipped silent
94111
# binaries (no output at all), which setup-soldr@v0.9.63
95112
# surfaced as a version-JSON parse failure.
96-
# Pin the latest published soldr release; setup-soldr's current
97-
# default can briefly lead a release that has not been published yet.
98-
# soldr v0.8.0 is the
99-
# known-good floor for the Apple SDK URL fix, soldr-clang-shim
100-
# in every release archive, cargo:rustc-env wrapper forwarding,
101-
# and managed cmake/ninja.
113+
# Pin a fully published soldr release. v0.9.5 is the first version
114+
# that consumes catalogue-v2 multipart assets; v0.8.23 still probes
115+
# the retired direct-LFS Apple SDK URLs even though the SDKs are
116+
# available through the v2 catalogue.
102117
uses: zackees/setup-soldr@v0
103118
with:
104-
version: 0.8.23
119+
version: 0.9.6
105120
cache: true
106121
build-cache: true
107122
target-cache: true
@@ -226,20 +241,12 @@ jobs:
226241
# cargo invocation so the SDKROOT export is debuggable independent
227242
# of the build itself.
228243
#
229-
# SOLDR_TOOLCHAIN_ORIGIN redirects soldr's toolchain catalogue
230-
# fetch away from the retired `manifest` branch of
231-
# zackees/soldr (soldr#988 Phase 5 / soldr#992, merged
232-
# 2026-06-27) at the new soldr-toolchain origin. soldr v0.7.66
233-
# honors this env var via the Phase 2 catalogue-first
234-
# fetch_once (soldr#989); older versions ignore it (and fail
235-
# against the retired manifest branch, which is what we're
236-
# avoiding here).
244+
# SOLDR_TOOLCHAIN_ORIGIN is job-scoped above so this prepare step and
245+
# both following build steps resolve the exact same catalogue.
237246
- name: Prepare Apple SDK (Linux → mac cross)
238247
if: inputs.mac_cross_linux
239248
timeout-minutes: 15
240249
shell: bash
241-
env:
242-
SOLDR_TOOLCHAIN_ORIGIN: https://zackees.github.io/soldr-toolchain
243250
run: soldr prepare --target ${{ inputs.target }}
244251

245252
- name: Build release binaries
@@ -301,6 +308,9 @@ jobs:
301308
# soldr's bin cache, which has been serving a corrupted
302309
# cargo-zigbuild binary (`Syntax error: ")" unexpected` at
303310
# line 10) and blocking PyPI publishes. See #331.
311+
# mimalloc-pprof's vendored diagnostic banner expands
312+
# __DATE__/__TIME__. Zig promotes that one warning to an error;
313+
# demote only that diagnostic for this C dependency build.
304314
cargo zigbuild --release --target ${{ inputs.target }} \
305315
-p fbuild-cli \
306316
-p fbuild-daemon

crates/fbuild-python/tests/pyo3_policy.rs

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fs;
1+
use std::{fs, ops::Range};
22

33
use fbuild_core::path::NormalizedPath;
44

@@ -13,6 +13,116 @@ fn repo_root() -> NormalizedPath {
1313
)
1414
}
1515

16+
fn yaml_indent(line: &str) -> usize {
17+
line.len() - line.trim_start().len()
18+
}
19+
20+
fn yaml_mapping_entry(line: &str) -> Option<(&str, &str)> {
21+
let content = line.trim_start();
22+
if content.is_empty() || content.starts_with(['#', '-']) {
23+
return None;
24+
}
25+
let (key, value) = content.split_once(':')?;
26+
Some((key.trim(), value.trim()))
27+
}
28+
29+
fn yaml_scalar(value: &str) -> &str {
30+
value
31+
.strip_prefix('"')
32+
.and_then(|value| value.strip_suffix('"'))
33+
.unwrap_or(value)
34+
}
35+
36+
fn yaml_significant(line: &str) -> bool {
37+
let content = line.trim_start();
38+
!content.is_empty() && !content.starts_with('#')
39+
}
40+
41+
fn yaml_mapping_block_in_scope(
42+
lines: &[&str],
43+
mut scope: Range<usize>,
44+
mut entry_indent: usize,
45+
path: &[&str],
46+
) -> Option<(Range<usize>, usize)> {
47+
for key in path {
48+
let entry = scope.clone().find(|&index| {
49+
yaml_indent(lines[index]) == entry_indent
50+
&& yaml_mapping_entry(lines[index]) == Some((*key, ""))
51+
})?;
52+
let end = ((entry + 1)..scope.end)
53+
.find(|&index| {
54+
yaml_significant(lines[index]) && yaml_indent(lines[index]) <= entry_indent
55+
})
56+
.unwrap_or(scope.end);
57+
scope = (entry + 1)..end;
58+
entry_indent += 2;
59+
}
60+
61+
Some((scope, entry_indent))
62+
}
63+
64+
fn yaml_mapping_block(lines: &[&str], path: &[&str]) -> Option<(Range<usize>, usize)> {
65+
yaml_mapping_block_in_scope(lines, 0..lines.len(), 0, path)
66+
}
67+
68+
fn yaml_mapping_value_in_scope<'a>(
69+
lines: &[&'a str],
70+
scope: Range<usize>,
71+
entry_indent: usize,
72+
path: &[&str],
73+
) -> Option<&'a str> {
74+
let (key, parent_path) = path.split_last()?;
75+
let (scope, entry_indent) =
76+
yaml_mapping_block_in_scope(lines, scope, entry_indent, parent_path)?;
77+
scope
78+
.filter_map(|index| {
79+
(yaml_indent(lines[index]) == entry_indent)
80+
.then(|| yaml_mapping_entry(lines[index]))
81+
.flatten()
82+
})
83+
.find_map(|(candidate, value)| (candidate == *key).then(|| yaml_scalar(value)))
84+
}
85+
86+
fn yaml_mapping_value<'a>(lines: &[&'a str], path: &[&str]) -> Option<&'a str> {
87+
yaml_mapping_value_in_scope(lines, 0..lines.len(), 0, path)
88+
}
89+
90+
fn yaml_sequence_values<'a>(lines: &[&'a str], path: &[&str]) -> Option<Vec<&'a str>> {
91+
let (scope, item_indent) = yaml_mapping_block(lines, path)?;
92+
Some(
93+
scope
94+
.filter_map(|index| {
95+
(yaml_indent(lines[index]) == item_indent)
96+
.then(|| lines[index].trim_start().strip_prefix("- "))
97+
.flatten()
98+
.map(yaml_scalar)
99+
})
100+
.collect(),
101+
)
102+
}
103+
104+
fn yaml_step_mapping_value<'a>(
105+
lines: &[&'a str],
106+
step_name: &str,
107+
path: &[&str],
108+
) -> Option<&'a str> {
109+
let (steps, item_indent) = yaml_mapping_block(lines, &["jobs", "build", "steps"])?;
110+
let step = steps.clone().find(|&index| {
111+
if yaml_indent(lines[index]) != item_indent {
112+
return false;
113+
}
114+
let Some(item) = lines[index].trim_start().strip_prefix("- ") else {
115+
return false;
116+
};
117+
yaml_mapping_entry(item)
118+
.is_some_and(|(key, value)| key == "name" && yaml_scalar(value) == step_name)
119+
})?;
120+
let step_end = ((step + 1)..steps.end)
121+
.find(|&index| yaml_significant(lines[index]) && yaml_indent(lines[index]) <= item_indent)
122+
.unwrap_or(steps.end);
123+
yaml_mapping_value_in_scope(lines, (step + 1)..step_end, item_indent + 2, path)
124+
}
125+
16126
#[test]
17127
fn pyo3_029_policy_stays_target_python_independent() {
18128
// FastLED/fbuild#1025: keep every cross-build branch explicit until
@@ -93,3 +203,62 @@ fn pyo3_029_policy_stays_target_python_independent() {
93203
);
94204
}
95205
}
206+
207+
#[test]
208+
fn native_release_workflow_uses_current_cross_toolchains() {
209+
let root = repo_root();
210+
let workflow =
211+
fs::read_to_string(root.join(".github/workflows/template_native_build.yml")).unwrap();
212+
let release_workflow =
213+
fs::read_to_string(root.join(".github/workflows/release-auto.yml")).unwrap();
214+
let workflow_lines = workflow.lines().collect::<Vec<_>>();
215+
let release_workflow_lines = release_workflow.lines().collect::<Vec<_>>();
216+
217+
assert_eq!(
218+
yaml_step_mapping_value(&workflow_lines, "Setup soldr", &["with", "version"]),
219+
Some("0.9.6"),
220+
"the setup-soldr step needs soldr >= 0.9.5 for catalogue-v2 Apple SDK assets"
221+
);
222+
assert_eq!(
223+
yaml_mapping_value(
224+
&workflow_lines,
225+
&["jobs", "build", "env", "SOLDR_TOOLCHAIN_ORIGIN"]
226+
),
227+
Some("https://zackees.github.io/soldr-toolchain"),
228+
"Apple SDK prepare and build steps must share a job-scoped catalogue origin"
229+
);
230+
for target in [
231+
"x86_64_unknown_linux_musl",
232+
"aarch64_unknown_linux_musl",
233+
"x86_64_apple_darwin",
234+
"aarch64_apple_darwin",
235+
] {
236+
let target_cflags = format!("CFLAGS_{target}");
237+
assert_eq!(
238+
yaml_mapping_value(
239+
&workflow_lines,
240+
&["jobs", "build", "env", target_cflags.as_str()]
241+
),
242+
Some("-Wno-error=date-time"),
243+
"zig cross builds need a job-scoped mimalloc-pprof diagnostic override: {target_cflags}"
244+
);
245+
}
246+
for job_limit in ["CARGO_BUILD_JOBS", "SOLDR_JOBS"] {
247+
assert_eq!(
248+
yaml_mapping_value(&workflow_lines, &["jobs", "build", "env", job_limit]),
249+
Some("1"),
250+
"native release lanes need a job-scoped hosted-runner memory limit: {job_limit}"
251+
);
252+
}
253+
let release_paths = yaml_sequence_values(&release_workflow_lines, &["on", "push", "paths"])
254+
.expect("release workflow must define on.push.paths");
255+
for release_input in [
256+
".github/workflows/release-auto.yml",
257+
".github/workflows/template_native_build.yml",
258+
] {
259+
assert!(
260+
release_paths.contains(&release_input),
261+
"release workflow fixes must retrigger an incomplete publication: {release_input}"
262+
);
263+
}
264+
}

python/fbuild/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
find_firmware,
2020
)
2121

22+
2223
__all__ = [
23-
"__version__",
2424
"AsyncDaemon",
2525
"AsyncDaemonConnection",
2626
"Daemon",
2727
"DaemonConnection",
28+
"__version__",
2829
"connect_daemon",
2930
"connect_daemon_async",
3031
"find_firmware",

0 commit comments

Comments
 (0)