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" ])
@@ -204,9 +206,9 @@ def eprint(*args, **kw):
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 ]]):
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 ):
224228 xtrace (args , env = env )
225229 sp .run (args , env = env , check = True )
226230
227231
228232def check_dup_targets ():
233+ """Ensure there are no duplicate targets in the list."""
229234 all = set ()
230235 duplicates = set ()
231236 for target in TARGETS :
@@ -235,7 +240,69 @@ def check_dup_targets():
235240 assert len (duplicates ) == 0 , f"duplicate targets: { duplicates } "
236241
237242
243+ def do_semver_checks (cfg : Cfg , target : Target ):
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+ cmd = ["cargo" , "rustdoc" , "--target" , tname ]
280+ rustdoc_args = ["--" , "-Zunstable-options" , "--output-format=json" ]
281+
282+ run ([* cmd , * rustdoc_args ], env = env | {"RUSTC_BOOTSTRAP" : "1" })
283+ run (
284+ [* cmd , "--manifest-path" , cfg .baseline_crate_dir , * rustdoc_args ],
285+ env = env | {"RUSTC_BOOTSTRAP" : "1" },
286+ )
287+ baseline = cfg .baseline_crate_dir / "target" / tname / "libc.json"
288+ current = Path ("target" ) / tname / "libc.json"
289+
290+ run (
291+ [
292+ "cargo" ,
293+ "semver-checks" ,
294+ "--only-explicit-features" ,
295+ "--features=std,extra_traits" ,
296+ "--baseline-rustdoc" ,
297+ baseline ,
298+ "--current-rustdoc" ,
299+ current ,
300+ ]
301+ )
302+
303+
238304def test_target (cfg : Cfg , target : Target ):
305+ """Run tests for a single target."""
239306 start = time .time ()
240307 env = os .environ .copy ()
241308 env .setdefault ("RUSTFLAGS" , "" )
@@ -303,22 +370,7 @@ def test_target(cfg: Cfg, target: Target):
303370 env = env | {"RUST_LIBC_UNSTABLE_FREEBSD_VERSION" : str (version )},
304371 )
305372
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" )
373+ do_semver_checks (cfg , target )
322374
323375 elapsed = round (time .time () - start , 2 )
324376 eprint (f"Finished checking target { tname } in { elapsed } seconds" )
@@ -329,6 +381,10 @@ def main():
329381 p .add_argument ("--toolchain" , required = True , help = "Rust toolchain" )
330382 p .add_argument ("--only" , help = "only targets matching this regex" )
331383 p .add_argument ("--skip" , help = "skip targets matching this regex" )
384+ p .add_argument (
385+ "--baseline-crate-dir" ,
386+ help = "specify the directory of the crate to run semver checks against" ,
387+ )
332388 p .add_argument (
333389 "--half" ,
334390 type = int ,
@@ -337,7 +393,7 @@ def main():
337393 )
338394 args = p .parse_args ()
339395
340- cfg = Cfg (toolchain_name = args .toolchain )
396+ cfg = Cfg (toolchain_name = args .toolchain , baseline_crate_dir = args . baseline_crate_dir )
341397 eprint (f"Config: { cfg } " )
342398 eprint ("Python version: " , sys .version )
343399 check_dup_targets ()
0 commit comments