@@ -20,9 +20,9 @@ use rustc_span::source_map::SourceMap;
2020use rustc_span:: symbol:: sym;
2121use rustc_span:: { BytePos , FileName , Pos , Span , DUMMY_SP } ;
2222use rustc_target:: spec:: { Target , TargetTriple } ;
23- use tempfile:: Builder as TempFileBuilder ;
2423
2524use std:: env;
25+ use std:: fs:: File ;
2626use std:: io:: { self , Write } ;
2727use std:: panic;
2828use std:: path:: { Path , PathBuf } ;
@@ -31,6 +31,8 @@ use std::str;
3131use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
3232use std:: sync:: { Arc , Mutex } ;
3333
34+ use tempfile:: { Builder as TempFileBuilder , TempDir } ;
35+
3436use crate :: clean:: { types:: AttributesExt , Attributes } ;
3537use crate :: config:: Options as RustdocOptions ;
3638use crate :: html:: markdown:: { self , ErrorCodes , Ignore , LangString } ;
@@ -48,7 +50,55 @@ pub(crate) struct GlobalTestOptions {
4850 pub ( crate ) attrs : Vec < String > ,
4951}
5052
51- pub ( crate ) fn run ( options : RustdocOptions ) -> Result < ( ) , ErrorGuaranteed > {
53+ pub ( crate ) fn generate_args_file ( file_path : & Path , options : & RustdocOptions ) -> Result < ( ) , String > {
54+ let mut file = File :: create ( file_path)
55+ . map_err ( |error| format ! ( "failed to create args file: {error:?}" ) ) ?;
56+
57+ // We now put the common arguments into the file we created.
58+ let mut content = vec ! [ "--crate-type=bin" . to_string( ) ] ;
59+
60+ for cfg in & options. cfgs {
61+ content. push ( format ! ( "--cfg={cfg}" ) ) ;
62+ }
63+ if !options. check_cfgs . is_empty ( ) {
64+ content. push ( "-Zunstable-options" . to_string ( ) ) ;
65+ for check_cfg in & options. check_cfgs {
66+ content. push ( format ! ( "--check-cfg={check_cfg}" ) ) ;
67+ }
68+ }
69+
70+ if let Some ( sysroot) = & options. maybe_sysroot {
71+ content. push ( format ! ( "--sysroot={}" , sysroot. display( ) ) ) ;
72+ }
73+ for lib_str in & options. lib_strs {
74+ content. push ( format ! ( "-L{lib_str}" ) ) ;
75+ }
76+ for extern_str in & options. extern_strs {
77+ content. push ( format ! ( "--extern={extern_str}" ) ) ;
78+ }
79+ content. push ( "-Ccodegen-units=1" . to_string ( ) ) ;
80+ for codegen_options_str in & options. codegen_options_strs {
81+ content. push ( format ! ( "-C{codegen_options_str}" ) ) ;
82+ }
83+ for unstable_option_str in & options. unstable_opts_strs {
84+ content. push ( format ! ( "-Z{unstable_option_str}" ) ) ;
85+ }
86+
87+ let content = content. join ( "\n " ) ;
88+
89+ file. write ( content. as_bytes ( ) )
90+ . map_err ( |error| format ! ( "failed to write arguments to temporary file: {error:?}" ) ) ?;
91+ Ok ( ( ) )
92+ }
93+
94+ fn get_doctest_dir ( ) -> io:: Result < TempDir > {
95+ TempFileBuilder :: new ( ) . prefix ( "rustdoctest" ) . tempdir ( )
96+ }
97+
98+ pub ( crate ) fn run (
99+ dcx : & rustc_errors:: DiagCtxt ,
100+ options : RustdocOptions ,
101+ ) -> Result < ( ) , ErrorGuaranteed > {
52102 let input = config:: Input :: File ( options. input . clone ( ) ) ;
53103
54104 let invalid_codeblock_attributes_name = crate :: lint:: INVALID_CODEBLOCK_ATTRIBUTES . name ;
@@ -118,6 +168,15 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
118168 let externs = options. externs . clone ( ) ;
119169 let json_unused_externs = options. json_unused_externs ;
120170
171+ let temp_dir = match get_doctest_dir ( )
172+ . map_err ( |error| format ! ( "failed to create temporary directory: {error:?}" ) )
173+ {
174+ Ok ( temp_dir) => temp_dir,
175+ Err ( error) => return crate :: wrap_return ( dcx, Err ( error) ) ,
176+ } ;
177+ let file_path = temp_dir. path ( ) . join ( "rustdoc-cfgs" ) ;
178+ crate :: wrap_return ( dcx, generate_args_file ( & file_path, & options) ) ?;
179+
121180 let ( tests, unused_extern_reports, compiling_test_count) =
122181 interface:: run_compiler ( config, |compiler| {
123182 compiler. enter ( |queries| {
@@ -134,6 +193,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
134193 Some ( compiler. sess . psess . clone_source_map ( ) ) ,
135194 None ,
136195 enable_per_target_ignores,
196+ file_path,
137197 ) ;
138198
139199 let mut hir_collector = HirCollector {
@@ -322,74 +382,53 @@ fn run_test(
322382 test : & str ,
323383 crate_name : & str ,
324384 line : usize ,
325- rustdoc_options : RustdocOptions ,
385+ rustdoc_options : IndividualTestOptions ,
326386 mut lang_string : LangString ,
327387 no_run : bool ,
328- runtool : Option < String > ,
329- runtool_args : Vec < String > ,
330- target : TargetTriple ,
331388 opts : & GlobalTestOptions ,
332389 edition : Edition ,
333- outdir : DirState ,
334390 path : PathBuf ,
335- test_id : & str ,
336391 report_unused_externs : impl Fn ( UnusedExterns ) ,
337392) -> Result < ( ) , TestFailure > {
338- let ( test, line_offset, supports_color) =
339- make_test ( test, Some ( crate_name) , lang_string. test_harness , opts, edition, Some ( test_id) ) ;
393+ let ( test, line_offset, supports_color) = make_test (
394+ test,
395+ Some ( crate_name) ,
396+ lang_string. test_harness ,
397+ opts,
398+ edition,
399+ Some ( & rustdoc_options. test_id ) ,
400+ ) ;
340401
341402 // Make sure we emit well-formed executable names for our target.
342- let rust_out = add_exe_suffix ( "rust_out" . to_owned ( ) , & target) ;
343- let output_file = outdir. path ( ) . join ( rust_out) ;
403+ let rust_out = add_exe_suffix ( "rust_out" . to_owned ( ) , & rustdoc_options . target ) ;
404+ let output_file = rustdoc_options . outdir . path ( ) . join ( rust_out) ;
344405
345406 let rustc_binary = rustdoc_options
346407 . test_builder
347408 . as_deref ( )
348409 . unwrap_or_else ( || rustc_interface:: util:: rustc_path ( ) . expect ( "found rustc" ) ) ;
349410 let mut compiler = wrapped_rustc_command ( & rustdoc_options. test_builder_wrappers , rustc_binary) ;
350- compiler. arg ( "--crate-type" ) . arg ( "bin" ) ;
351- for cfg in & rustdoc_options. cfgs {
352- compiler. arg ( "--cfg" ) . arg ( & cfg) ;
353- }
354- if !rustdoc_options. check_cfgs . is_empty ( ) {
355- compiler. arg ( "-Z" ) . arg ( "unstable-options" ) ;
356- for check_cfg in & rustdoc_options. check_cfgs {
357- compiler. arg ( "--check-cfg" ) . arg ( & check_cfg) ;
358- }
359- }
360- if let Some ( sysroot) = rustdoc_options. maybe_sysroot {
361- compiler. arg ( "--sysroot" ) . arg ( sysroot) ;
362- }
411+
412+ compiler. arg ( & format ! ( "@{}" , rustdoc_options. arg_file. display( ) ) ) ;
413+
363414 compiler. arg ( "--edition" ) . arg ( & edition. to_string ( ) ) ;
364415 compiler. env ( "UNSTABLE_RUSTDOC_TEST_PATH" , path) ;
365416 compiler. env ( "UNSTABLE_RUSTDOC_TEST_LINE" , format ! ( "{}" , line as isize - line_offset as isize ) ) ;
366417 compiler. arg ( "-o" ) . arg ( & output_file) ;
367418 if lang_string. test_harness {
368419 compiler. arg ( "--test" ) ;
369420 }
370- if rustdoc_options. json_unused_externs . is_enabled ( ) && !lang_string. compile_fail {
421+ if rustdoc_options. is_json_unused_externs_enabled && !lang_string. compile_fail {
371422 compiler. arg ( "--error-format=json" ) ;
372423 compiler. arg ( "--json" ) . arg ( "unused-externs" ) ;
373- compiler. arg ( "-Z" ) . arg ( "unstable-options" ) ;
374424 compiler. arg ( "-W" ) . arg ( "unused_crate_dependencies" ) ;
425+ compiler. arg ( "-Z" ) . arg ( "unstable-options" ) ;
375426 }
376- for lib_str in & rustdoc_options. lib_strs {
377- compiler. arg ( "-L" ) . arg ( & lib_str) ;
378- }
379- for extern_str in & rustdoc_options. extern_strs {
380- compiler. arg ( "--extern" ) . arg ( & extern_str) ;
381- }
382- compiler. arg ( "-Ccodegen-units=1" ) ;
383- for codegen_options_str in & rustdoc_options. codegen_options_strs {
384- compiler. arg ( "-C" ) . arg ( & codegen_options_str) ;
385- }
386- for unstable_option_str in & rustdoc_options. unstable_opts_strs {
387- compiler. arg ( "-Z" ) . arg ( & unstable_option_str) ;
388- }
389- if no_run && !lang_string. compile_fail && rustdoc_options. persist_doctests . is_none ( ) {
427+
428+ if no_run && !lang_string. compile_fail && rustdoc_options. should_persist_doctests {
390429 compiler. arg ( "--emit=metadata" ) ;
391430 }
392- compiler. arg ( "--target" ) . arg ( match target {
431+ compiler. arg ( "--target" ) . arg ( match rustdoc_options . target {
393432 TargetTriple :: TargetTriple ( s) => s,
394433 TargetTriple :: TargetJson { path_for_rustdoc, .. } => {
395434 path_for_rustdoc. to_str ( ) . expect ( "target path must be valid unicode" ) . to_string ( )
@@ -485,10 +524,10 @@ fn run_test(
485524 let mut cmd;
486525
487526 let output_file = make_maybe_absolute_path ( output_file) ;
488- if let Some ( tool) = runtool {
527+ if let Some ( tool) = rustdoc_options . runtool {
489528 let tool = make_maybe_absolute_path ( tool. into ( ) ) ;
490529 cmd = Command :: new ( tool) ;
491- cmd. args ( runtool_args) ;
530+ cmd. args ( rustdoc_options . runtool_args ) ;
492531 cmd. arg ( output_file) ;
493532 } else {
494533 cmd = Command :: new ( output_file) ;
@@ -897,6 +936,56 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
897936 ( before, after, crates)
898937}
899938
939+ pub ( crate ) struct IndividualTestOptions {
940+ test_builder : Option < PathBuf > ,
941+ test_builder_wrappers : Vec < PathBuf > ,
942+ is_json_unused_externs_enabled : bool ,
943+ should_persist_doctests : bool ,
944+ error_format : ErrorOutputType ,
945+ test_run_directory : Option < PathBuf > ,
946+ nocapture : bool ,
947+ arg_file : PathBuf ,
948+ outdir : DirState ,
949+ runtool : Option < String > ,
950+ runtool_args : Vec < String > ,
951+ target : TargetTriple ,
952+ test_id : String ,
953+ }
954+
955+ impl IndividualTestOptions {
956+ fn new ( options : & RustdocOptions , arg_file : & Path , test_id : String ) -> Self {
957+ let outdir = if let Some ( ref path) = options. persist_doctests {
958+ let mut path = path. clone ( ) ;
959+ path. push ( & test_id) ;
960+
961+ if let Err ( err) = std:: fs:: create_dir_all ( & path) {
962+ eprintln ! ( "Couldn't create directory for doctest executables: {err}" ) ;
963+ panic:: resume_unwind ( Box :: new ( ( ) ) ) ;
964+ }
965+
966+ DirState :: Perm ( path)
967+ } else {
968+ DirState :: Temp ( get_doctest_dir ( ) . expect ( "rustdoc needs a tempdir" ) )
969+ } ;
970+
971+ Self {
972+ test_builder : options. test_builder . clone ( ) ,
973+ test_builder_wrappers : options. test_builder_wrappers . clone ( ) ,
974+ is_json_unused_externs_enabled : options. json_unused_externs . is_enabled ( ) ,
975+ should_persist_doctests : options. persist_doctests . is_none ( ) ,
976+ error_format : options. error_format ,
977+ test_run_directory : options. test_run_directory . clone ( ) ,
978+ nocapture : options. nocapture ,
979+ arg_file : arg_file. into ( ) ,
980+ outdir,
981+ runtool : options. runtool . clone ( ) ,
982+ runtool_args : options. runtool_args . clone ( ) ,
983+ target : options. target . clone ( ) ,
984+ test_id,
985+ }
986+ }
987+ }
988+
900989pub ( crate ) trait Tester {
901990 fn add_test ( & mut self , test : String , config : LangString , line : usize ) ;
902991 fn get_line ( & self ) -> usize {
@@ -941,6 +1030,7 @@ pub(crate) struct Collector {
9411030 visited_tests : FxHashMap < ( String , usize ) , usize > ,
9421031 unused_extern_reports : Arc < Mutex < Vec < UnusedExterns > > > ,
9431032 compiling_test_count : AtomicUsize ,
1033+ arg_file : PathBuf ,
9441034}
9451035
9461036impl Collector {
@@ -952,6 +1042,7 @@ impl Collector {
9521042 source_map : Option < Lrc < SourceMap > > ,
9531043 filename : Option < PathBuf > ,
9541044 enable_per_target_ignores : bool ,
1045+ arg_file : PathBuf ,
9551046 ) -> Collector {
9561047 Collector {
9571048 tests : Vec :: new ( ) ,
@@ -967,6 +1058,7 @@ impl Collector {
9671058 visited_tests : FxHashMap :: default ( ) ,
9681059 unused_extern_reports : Default :: default ( ) ,
9691060 compiling_test_count : AtomicUsize :: new ( 0 ) ,
1061+ arg_file,
9701062 }
9711063 }
9721064
@@ -1009,13 +1101,9 @@ impl Tester for Collector {
10091101 let crate_name = self . crate_name . clone ( ) ;
10101102 let opts = self . opts . clone ( ) ;
10111103 let edition = config. edition . unwrap_or ( self . rustdoc_options . edition ) ;
1012- let rustdoc_options = self . rustdoc_options . clone ( ) ;
1013- let runtool = self . rustdoc_options . runtool . clone ( ) ;
1014- let runtool_args = self . rustdoc_options . runtool_args . clone ( ) ;
1015- let target = self . rustdoc_options . target . clone ( ) ;
1016- let target_str = target. to_string ( ) ;
1104+ let target_str = self . rustdoc_options . target . to_string ( ) ;
10171105 let unused_externs = self . unused_extern_reports . clone ( ) ;
1018- let no_run = config. no_run || rustdoc_options. no_run ;
1106+ let no_run = config. no_run || self . rustdoc_options . no_run ;
10191107 if !config. compile_fail {
10201108 self . compiling_test_count . fetch_add ( 1 , Ordering :: SeqCst ) ;
10211109 }
@@ -1049,23 +1137,9 @@ impl Tester for Collector {
10491137 self . visited_tests. entry( ( file. clone( ) , line) ) . and_modify( |v| * v += 1 ) . or_insert( 0 )
10501138 } ,
10511139 ) ;
1052- let outdir = if let Some ( mut path) = rustdoc_options. persist_doctests . clone ( ) {
1053- path. push ( & test_id) ;
10541140
1055- if let Err ( err) = std:: fs:: create_dir_all ( & path) {
1056- eprintln ! ( "Couldn't create directory for doctest executables: {err}" ) ;
1057- panic:: resume_unwind ( Box :: new ( ( ) ) ) ;
1058- }
1059-
1060- DirState :: Perm ( path)
1061- } else {
1062- DirState :: Temp (
1063- TempFileBuilder :: new ( )
1064- . prefix ( "rustdoctest" )
1065- . tempdir ( )
1066- . expect ( "rustdoc needs a tempdir" ) ,
1067- )
1068- } ;
1141+ let rustdoc_test_options =
1142+ IndividualTestOptions :: new ( & self . rustdoc_options , & self . arg_file , test_id) ;
10691143
10701144 debug ! ( "creating test {name}: {test}" ) ;
10711145 self . tests . push ( test:: TestDescAndFn {
@@ -1096,17 +1170,12 @@ impl Tester for Collector {
10961170 & test,
10971171 & crate_name,
10981172 line,
1099- rustdoc_options ,
1173+ rustdoc_test_options ,
11001174 config,
11011175 no_run,
1102- runtool,
1103- runtool_args,
1104- target,
11051176 & opts,
11061177 edition,
1107- outdir,
11081178 path,
1109- & test_id,
11101179 report_unused_externs,
11111180 ) ;
11121181
0 commit comments