99import time
1010from dataclasses import dataclass , field
1111from enum import Enum , IntEnum
12- from typing import Optional
12+ from pathlib import Path
13+ from typing import Optional , Sequence
1314
1415
1516ESC_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