Skip to content

Commit e164412

Browse files
committed
ci: Run semver checks even on non-host targets
1 parent 83bd32f commit e164412

File tree

3 files changed

+141
-32
lines changed

3 files changed

+141
-32
lines changed

.github/workflows/ci.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ env:
1717
RUSTDOCFLAGS: -Dwarnings
1818
RUSTFLAGS: -Dwarnings
1919
RUST_BACKTRACE: full
20+
SERIES: "1.0" # 1.0 alpha series
2021

2122
defaults:
2223
run:
@@ -79,6 +80,10 @@ jobs:
7980
uses: taiki-e/install-action@cargo-semver-checks
8081
if: matrix.toolchain == 'stable'
8182

83+
- name: Retrieve semver baseline
84+
if: matrix.toolchain == 'stable'
85+
run: ./ci/semver.sh
86+
8287
# FIXME(ci): These `du` statements are temporary for debugging cache
8388
- name: Target size before restoring cache
8489
run: du -sh target | sort -k 2 || true
@@ -100,6 +105,7 @@ jobs:
100105
101106
python3 ci/verify-build.py \
102107
--toolchain "$TOOLCHAIN" \
108+
${BASELINE_CRATE_DIR:+"--baseline-crate-dir" "$BASELINE_CRATE_DIR"} \
103109
${{ matrix.only && format('--only "{0}"', matrix.only) }} \
104110
${{ matrix.half && format('--half "{0}"', matrix.half) }}
105111
- name: Target size after job completion

ci/semver.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
# Download a baseline crate to run semver checks against
3+
4+
set -euxo pipefail
5+
6+
# https://crates.io/data-access#api
7+
8+
# Need to include versions to get the default and stable versions
9+
meta=$(curl -L https://index.crates.io/li/bc/libc)
10+
11+
# Versions to check against
12+
if [ "${SERIES:-}" = "0.2" ]; then
13+
pat="^0.2"
14+
elif [ "${SERIES:-}" = "1.0" ]; then
15+
pat="^1.0"
16+
else
17+
echo "SERIES must be set to either '0.2' or '1.0'"
18+
exit 1
19+
fi
20+
21+
# Find the most recent version matching a pattern.
22+
release=$(
23+
echo "$meta" |
24+
jq -er --slurp --arg pat "$pat" 'map(select(.vers | test($pat))) | last'
25+
)
26+
version=$(echo "$release" | jq -r '.vers')
27+
28+
crate_dir="libc-$version"
29+
curl -L "https://static.crates.io/crates/libc/libc-$version.crate" | tar xzf -
30+
31+
# Need to convince Cargo it's not part of our workspace
32+
echo '[workspace]' >> "$crate_dir/Cargo.toml"
33+
34+
echo "BASELINE_CRATE_DIR=$(realpath "$crate_dir")" >> "$GITHUB_ENV"

ci/verify-build.py

Lines changed: 101 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import time
1010
from dataclasses import dataclass, field
1111
from enum import Enum, IntEnum
12-
from typing import Optional
12+
from pathlib import Path
13+
from typing import Optional, Sequence
1314

1415

1516
ESC_CYAN = "\033[1;36m"
@@ -35,6 +36,7 @@ class Cfg:
3536
toolchain: Toolchain = field(init=False)
3637
host_target: str = field(init=False)
3738
os_: Os = field(init=False)
39+
baseline_crate_dir: Optional[Path]
3840

3941
def __post_init__(self):
4042
rustc_output = check_output(["rustc", f"+{self.toolchain_name}", "-vV"])
@@ -200,13 +202,13 @@ def __post_init__(self):
200202
]
201203

202204

203-
def eprint(*args, **kw):
205+
def eprint(*args, **kw) -> None:
204206
print(*args, file=sys.stderr, **kw)
205207

206208

207-
def xtrace(args: list[str], /, env: Optional[dict[str, str]]):
209+
def xtrace(args: Sequence[str | Path], /, env: Optional[dict[str, str]]) -> None:
208210
"""Print commands before running them."""
209-
astr = " ".join(args)
211+
astr = " ".join(str(arg) for arg in args)
210212
if env is None:
211213
eprint(f"+ {astr}")
212214
else:
@@ -215,17 +217,20 @@ def xtrace(args: list[str], /, env: Optional[dict[str, str]]):
215217
eprint(f"+ {estr} {astr}")
216218

217219

218-
def check_output(args: list[str], /, env: Optional[dict[str, str]] = None) -> str:
220+
def check_output(
221+
args: Sequence[str | Path], /, env: Optional[dict[str, str]] = None
222+
) -> str:
219223
xtrace(args, env=env)
220224
return sp.check_output(args, env=env, encoding="utf8")
221225

222226

223-
def run(args: list[str], /, env: Optional[dict[str, str]] = None):
227+
def run(args: Sequence[str | Path], /, env: Optional[dict[str, str]] = None) -> None:
224228
xtrace(args, env=env)
225229
sp.run(args, env=env, check=True)
226230

227231

228-
def check_dup_targets():
232+
def check_dup_targets() -> None:
233+
"""Ensure there are no duplicate targets in the list."""
229234
all = set()
230235
duplicates = set()
231236
for target in TARGETS:
@@ -235,7 +240,78 @@ def check_dup_targets():
235240
assert len(duplicates) == 0, f"duplicate targets: {duplicates}"
236241

237242

238-
def test_target(cfg: Cfg, target: Target):
243+
def do_semver_checks(cfg: Cfg, target: Target) -> None:
244+
tname = target.name
245+
if cfg.toolchain != Toolchain.STABLE:
246+
eprint("Skipping semver checks")
247+
return
248+
249+
if tname == cfg.host_target:
250+
# FIXME(semver): can't pass `--target` to `cargo-semver-checks` so we can only
251+
# run directly on the host target
252+
eprint("Running semver checks on host")
253+
run(
254+
[
255+
"cargo",
256+
"semver-checks",
257+
"--only-explicit-features",
258+
"--features=std,extra_traits",
259+
]
260+
)
261+
return
262+
263+
if cfg.baseline_crate_dir is None:
264+
eprint(
265+
"Non-host target: --baseline-crate-dir must be specified to \
266+
run semver-checks"
267+
)
268+
return
269+
270+
if not target.dist:
271+
eprint("Skipping semver checks on non-dist target")
272+
return
273+
274+
# Since semver-checks doesn't work with `--target`, we build the json our self and
275+
# hand it over.
276+
eprint("Running semver checks with cross compilation")
277+
278+
env = os.environ.copy()
279+
env.setdefault("RUSTFLAGS", "")
280+
env.setdefault("RUSTDOCFLAGS", "")
281+
# Needed for rustdoc json
282+
env["RUSTC_BOOTSTRAP"] = "1"
283+
# Unset everything that would cause us to get warnings for the baseline
284+
env["RUSTFLAGS"] += " -Awarnings"
285+
env["RUSTDOCFLAGS"] += " -Awarnings"
286+
env.pop("LIBC_CI", None)
287+
288+
cmd = ["cargo", "rustdoc", "--target", tname]
289+
rustdoc_args = ["--", "-Zunstable-options", "--output-format=json"]
290+
291+
# Build the current crate and the baseline crate, which CI should have downloaded
292+
run([*cmd, *rustdoc_args], env=env)
293+
run(
294+
[*cmd, "--manifest-path", cfg.baseline_crate_dir / "Cargo.toml", *rustdoc_args],
295+
env=env,
296+
)
297+
298+
baseline = cfg.baseline_crate_dir / "target" / tname / "doc" / "libc.json"
299+
current = Path("target") / tname / "doc" / "libc.json"
300+
301+
run(
302+
[
303+
"cargo",
304+
"semver-checks",
305+
"--baseline-rustdoc",
306+
baseline,
307+
"--current-rustdoc",
308+
current,
309+
]
310+
)
311+
312+
313+
def test_target(cfg: Cfg, target: Target) -> None:
314+
"""Run tests for a single target."""
239315
start = time.time()
240316
env = os.environ.copy()
241317
env.setdefault("RUSTFLAGS", "")
@@ -261,14 +337,15 @@ def test_target(cfg: Cfg, target: Target):
261337
if not target.dist:
262338
# If we can't download a `core`, we need to build it
263339
cmd += ["-Zbuild-std=core"]
264-
# FIXME: With `build-std` feature, `compiler_builtins` emits a lot of lint warnings.
340+
# FIXME: With `the build-std` feature, `compiler_builtins` emits a lot of
341+
# lint warnings.
265342
env["RUSTFLAGS"] += " -Aimproper_ctypes_definitions"
266343
else:
267344
run(["rustup", "target", "add", tname, "--toolchain", cfg.toolchain_name])
268345

269346
# Test with expected combinations of features
270347
run(cmd, env=env)
271-
run(cmd + ["--features=extra_traits"], env=env)
348+
run([*cmd, "--features=extra_traits"], env=env)
272349

273350
# Check with different env for 64-bit time_t
274351
if target_os == "linux" and target_bits == "32":
@@ -286,49 +363,38 @@ def test_target(cfg: Cfg, target: Target):
286363
run(cmd, env=env | {"RUST_LIBC_UNSTABLE_MUSL_V1_2_3": "1"})
287364

288365
# Test again without default features, i.e. without `std`
289-
run(cmd + ["--no-default-features"])
290-
run(cmd + ["--no-default-features", "--features=extra_traits"])
366+
run([*cmd, "--no-default-features"])
367+
run([*cmd, "--no-default-features", "--features=extra_traits"])
291368

292369
# Ensure the crate will build when used as a dependency of `std`
293370
if cfg.nightly():
294-
run(cmd + ["--no-default-features", "--features=rustc-dep-of-std"])
371+
run([*cmd, "--no-default-features", "--features=rustc-dep-of-std"])
295372

296373
# For freebsd targets, check with the different versions we support
297374
# if on nightly or stable
298375
if "freebsd" in tname and cfg.toolchain >= Toolchain.STABLE:
299376
for version in FREEBSD_VERSIONS:
300377
run(cmd, env=env | {"RUST_LIBC_UNSTABLE_FREEBSD_VERSION": str(version)})
301378
run(
302-
cmd + ["--no-default-features"],
379+
[*cmd, "--no-default-features"],
303380
env=env | {"RUST_LIBC_UNSTABLE_FREEBSD_VERSION": str(version)},
304381
)
305382

306-
is_stable = cfg.toolchain == Toolchain.STABLE
307-
# FIXME(semver): can't pass `--target` to `cargo-semver-checks` so we restrict to
308-
# the host target
309-
is_host = tname == cfg.host_target
310-
if is_stable and is_host:
311-
eprint("Running semver checks")
312-
run(
313-
[
314-
"cargo",
315-
"semver-checks",
316-
"--only-explicit-features",
317-
"--features=std,extra_traits",
318-
]
319-
)
320-
else:
321-
eprint("Skipping semver checks")
383+
do_semver_checks(cfg, target)
322384

323385
elapsed = round(time.time() - start, 2)
324386
eprint(f"Finished checking target {tname} in {elapsed} seconds")
325387

326388

327-
def main():
389+
def main() -> None:
328390
p = argparse.ArgumentParser()
329391
p.add_argument("--toolchain", required=True, help="Rust toolchain")
330392
p.add_argument("--only", help="only targets matching this regex")
331393
p.add_argument("--skip", help="skip targets matching this regex")
394+
p.add_argument(
395+
"--baseline-crate-dir",
396+
help="specify the directory of the crate to run semver checks against",
397+
)
332398
p.add_argument(
333399
"--half",
334400
type=int,
@@ -337,7 +403,10 @@ def main():
337403
)
338404
args = p.parse_args()
339405

340-
cfg = Cfg(toolchain_name=args.toolchain)
406+
cfg = Cfg(
407+
toolchain_name=args.toolchain,
408+
baseline_crate_dir=args.baseline_crate_dir and Path(args.baseline_crate_dir),
409+
)
341410
eprint(f"Config: {cfg}")
342411
eprint("Python version: ", sys.version)
343412
check_dup_targets()

0 commit comments

Comments
 (0)