@@ -263,7 +263,9 @@ struct UnitsNixTemplate {
263263 library_entries : String ,
264264 benchmark_entries : String ,
265265 test_entries : String ,
266+ doctest_entries : String ,
266267 test_target_entries : String ,
268+ doctest_target_entries : String ,
267269 benchmark_target_entries : String ,
268270 target_set_entries : String ,
269271 default_entry : String ,
@@ -284,7 +286,9 @@ pub fn render_units_nix(graph: &UnitGraph, options: &RenderOptions) -> Result<St
284286 library_entries : render_root_entries ( graph, & prepared, Unit :: is_library) ,
285287 benchmark_entries : render_benchmark_entries ( graph, & prepared) ,
286288 test_entries : render_test_entries ( graph, & prepared) ,
289+ doctest_entries : render_doctest_entries ( graph, & prepared) ,
287290 test_target_entries : render_test_target_entries ( graph, & prepared) ,
291+ doctest_target_entries : render_doctest_target_entries ( graph, & prepared) ?,
288292 benchmark_target_entries : render_benchmark_target_entries ( graph, & prepared) ,
289293 target_set_entries : render_target_sets ( graph, & prepared) ,
290294 default_entry : render_default_entry ( graph, & prepared) ,
@@ -1313,6 +1317,10 @@ fn nix_indented_string_fragment(value: &str) -> String {
13131317 value. replace ( "''" , "'''" ) . replace ( "${" , "''${" )
13141318}
13151319
1320+ fn nix_indented_string ( value : & str ) -> String {
1321+ format ! ( "''\n {value}''" )
1322+ }
1323+
13161324#[ derive( Default ) ]
13171325struct CargoManifestPackageMetadata {
13181326 authors : String ,
@@ -2083,17 +2091,20 @@ fn render_target_sets(graph: &UnitGraph, prepared: &PreparedGraph) -> String {
20832091 } ;
20842092 let test_keys = compute_test_keys ( graph, prepared) ;
20852093 let benchmark_keys = compute_benchmark_keys ( graph, prepared) ;
2094+ let doctest_keys = compute_doctest_keys ( graph, prepared) ;
2095+ let doctest_indexes = doctest_keys. keys ( ) . copied ( ) . collect :: < Vec < _ > > ( ) ;
20862096
20872097 root_sets
20882098 . iter ( )
20892099 . map ( |roots| {
20902100 format ! (
2091- " {{\n roots = [ {} ];\n binaries = {{\n {} }};\n libraries = {{\n {} }};\n benchmarks = {{\n {} }};\n tests = {{\n {} }};\n }}" ,
2101+ " {{\n roots = [ {} ];\n binaries = {{\n {} }};\n libraries = {{\n {} }};\n benchmarks = {{\n {} }};\n tests = {{\n {} }};\n doctests = {{ \n {} }}; \n }}" ,
20922102 render_unit_refs( roots, prepared) ,
20932103 render_root_entries_for( roots, & graph. units, prepared, Unit :: is_bin) ,
20942104 render_root_entries_for( roots, & graph. units, prepared, Unit :: is_library) ,
20952105 render_benchmark_entries_for( roots, & graph. units, prepared, & benchmark_keys) ,
20962106 render_test_entries_for( roots, & graph. units, prepared, & test_keys) ,
2107+ render_doctest_entries_for( & doctest_indexes, & graph. units, & doctest_keys) ,
20972108 )
20982109 } )
20992110 . collect :: < Vec < _ > > ( )
@@ -2149,6 +2160,117 @@ fn test_binary_expr(unit: &Unit, prepared: &PreparedGraph, index: usize) -> Stri
21492160 format ! ( "{unit_ref}/bin/{}" , unit. target. name)
21502161}
21512162
2163+ fn render_doctest_command (
2164+ graph : & UnitGraph ,
2165+ prepared : & PreparedGraph ,
2166+ index : usize ,
2167+ mode : DoctestCommandMode ,
2168+ ) -> Result < String > {
2169+ let unit = & graph. units [ index] ;
2170+ let source = prepared. source_entry ( index) ?;
2171+ let package_root = source_path_expr ( source, & crate_root_for_unit ( unit) ) ?;
2172+ let source_path = source_path_expr ( source, Path :: new ( & unit. target . src_path ) ) ?;
2173+ let unit_ref = format ! ( "${{units.{}}}" , nix_attr( & prepared. names[ index] ) ) ;
2174+ let mut script = String :: new ( ) ;
2175+
2176+ script. push_str ( "set -euo pipefail\n " ) ;
2177+ writeln ! ( script, "export src=\" ${{{}}}\" " , prepared. source_ref( index) ) ?;
2178+ writeln ! (
2179+ script,
2180+ "export CARGO_MANIFEST_DIR={}" ,
2181+ shell:: double_quote( & package_root)
2182+ ) ?;
2183+ script. push_str ( "cd \" $CARGO_MANIFEST_DIR\" \n " ) ;
2184+ script. push_str ( & cargo_package_exports ( unit) ?) ;
2185+ script. push_str ( "rustdoc_args=( --test -Z unstable-options )\n " ) ;
2186+ match mode {
2187+ DoctestCommandMode :: List => {
2188+ script. push_str ( "rustdoc_args+=( --output-format doctest )\n " ) ;
2189+ }
2190+ DoctestCommandMode :: RunAll => { }
2191+ DoctestCommandMode :: RunCase => {
2192+ script
2193+ . push_str ( "rustdoc_args+=( --test-args \" $TEST_NAME\" --test-args --nocapture )\n " ) ;
2194+ }
2195+ }
2196+ push_rustdoc_arg ( & mut script, "--crate-name" ) ;
2197+ push_rustdoc_arg ( & mut script, & unit. target . name . replace ( '-' , "_" ) ) ;
2198+ push_rustdoc_arg ( & mut script, "--edition" ) ;
2199+ push_rustdoc_arg ( & mut script, & unit. target . edition ) ;
2200+ for feature in & unit. features {
2201+ push_rustdoc_arg ( & mut script, "--cfg" ) ;
2202+ push_rustdoc_arg ( & mut script, & format ! ( "feature=\" {feature}\" " ) ) ;
2203+ }
2204+ if let Some ( platform) = & unit. platform {
2205+ push_rustdoc_arg ( & mut script, "--target" ) ;
2206+ push_rustdoc_arg ( & mut script, platform) ;
2207+ }
2208+ for rustflag in & unit. profile . rustflags {
2209+ push_rustdoc_arg ( & mut script, "--doctest-build-arg" ) ;
2210+ push_rustdoc_arg ( & mut script, rustflag) ;
2211+ }
2212+ for dep_index in & prepared. transitive_unit_deps [ index] {
2213+ let dep = & graph. units [ * dep_index] ;
2214+ if dep. is_bin ( ) {
2215+ continue ;
2216+ }
2217+ writeln ! (
2218+ script,
2219+ "rustdoc_args+=( -L \" dependency=${{units.{}}}/lib\" )" ,
2220+ nix_attr( & prepared. names[ * dep_index] )
2221+ ) ?;
2222+ }
2223+ writeln ! ( script, "rustdoc_args+=( -L \" dependency={unit_ref}/lib\" )" ) ?;
2224+ writeln ! (
2225+ script,
2226+ "rustdoc_args+=( --extern \" {}=$(cat {unit_ref}/nix-support/extern-path)\" )" ,
2227+ unit. target. name. replace( '-' , "_" )
2228+ ) ?;
2229+ for dependency in & unit. dependencies {
2230+ let dep_unit = & graph. units [ dependency. index ] ;
2231+ if dep_unit. is_run_custom_build ( ) || dep_unit. is_bin ( ) {
2232+ continue ;
2233+ }
2234+ writeln ! (
2235+ script,
2236+ "rustdoc_args+=( --extern \" {}=$(cat ${{units.{}}}/nix-support/extern-path)\" )" ,
2237+ dependency. extern_crate_name,
2238+ nix_attr( & prepared. names[ dependency. index] )
2239+ ) ?;
2240+ }
2241+ writeln ! (
2242+ script,
2243+ "rustdoc_args+=( {} )" ,
2244+ shell:: double_quote( & source_path)
2245+ ) ?;
2246+ script. push_str ( "set -x\n " ) ;
2247+ match mode {
2248+ DoctestCommandMode :: RunCase => {
2249+ script. push_str ( "doctest_log=$(mktemp)\n " ) ;
2250+ script. push_str ( "rustdoc \" ''${rustdoc_args[@]}\" 2>&1 | tee \" $doctest_log\" \n " ) ;
2251+ script. push_str ( "if grep -q 'running 0 tests' \" $doctest_log\" ; then\n " ) ;
2252+ script. push_str ( " echo \" rustdoc filter did not run a doctest: $TEST_NAME\" >&2\n " ) ;
2253+ script. push_str ( " exit 1\n " ) ;
2254+ script. push_str ( "fi\n " ) ;
2255+ }
2256+ DoctestCommandMode :: List | DoctestCommandMode :: RunAll => {
2257+ script. push_str ( "rustdoc \" ''${rustdoc_args[@]}\" \n " ) ;
2258+ }
2259+ }
2260+ Ok ( script)
2261+ }
2262+
2263+ #[ derive( Clone , Copy ) ]
2264+ enum DoctestCommandMode {
2265+ List ,
2266+ RunAll ,
2267+ RunCase ,
2268+ }
2269+
2270+ fn push_rustdoc_arg ( script : & mut String , value : & str ) {
2271+ let _ = writeln ! ( script, "rustdoc_args+=( {} )" , shell:: quote( value) ) ;
2272+ }
2273+
21522274fn render_benchmark_entries_for (
21532275 roots : & [ usize ] ,
21542276 units : & [ Unit ] ,
@@ -2222,6 +2344,73 @@ fn render_test_entries(graph: &UnitGraph, prepared: &PreparedGraph) -> String {
22222344 render_test_entries_for ( & graph. roots , & graph. units , prepared, & keys)
22232345}
22242346
2347+ fn compute_doctest_keys ( graph : & UnitGraph , prepared : & PreparedGraph ) -> BTreeMap < usize , String > {
2348+ let indexes: Vec < usize > = graph
2349+ . units
2350+ . iter ( )
2351+ . enumerate ( )
2352+ . filter_map ( |( index, unit) | unit. has_doctests ( ) . then_some ( index) )
2353+ . collect ( ) ;
2354+
2355+ let mut counts: BTreeMap < & str , usize > = BTreeMap :: new ( ) ;
2356+ for index in & indexes {
2357+ * counts
2358+ . entry ( graph. units [ * index] . target . name . as_str ( ) )
2359+ . or_insert ( 0 ) += 1 ;
2360+ }
2361+
2362+ let mut keys = BTreeMap :: new ( ) ;
2363+ for index in indexes {
2364+ let unit = & graph. units [ index] ;
2365+ let key = if counts[ unit. target . name . as_str ( ) ] == 1 {
2366+ unit. target . name . clone ( )
2367+ } else {
2368+ prepared. names [ index] . clone ( )
2369+ } ;
2370+ keys. insert ( index, key) ;
2371+ }
2372+ keys
2373+ }
2374+
2375+ fn render_doctest_entries_for (
2376+ roots : & [ usize ] ,
2377+ units : & [ Unit ] ,
2378+ keys : & BTreeMap < usize , String > ,
2379+ ) -> String {
2380+ let mut entries = String :: new ( ) ;
2381+ let mut seen = BTreeSet :: new ( ) ;
2382+ for index in roots {
2383+ let unit = & units[ * index] ;
2384+ if !unit. has_doctests ( ) {
2385+ continue ;
2386+ }
2387+ let key = keys
2388+ . get ( index)
2389+ . expect ( "compute_doctest_keys covers every root doctest unit" )
2390+ . clone ( ) ;
2391+ if !seen. insert ( key. clone ( ) ) {
2392+ continue ;
2393+ }
2394+ let _ = writeln ! (
2395+ entries,
2396+ " {} = mkDoctestEntry (builtins.head (builtins.filter (target: target.name == {}) doctestTargets));" ,
2397+ nix_attr( & key) ,
2398+ nix_attr( & key) ,
2399+ ) ;
2400+ }
2401+
2402+ entries
2403+ }
2404+
2405+ fn render_doctest_entries ( graph : & UnitGraph , prepared : & PreparedGraph ) -> String {
2406+ let keys = compute_doctest_keys ( graph, prepared) ;
2407+ render_doctest_entries_for (
2408+ & keys. keys ( ) . copied ( ) . collect :: < Vec < _ > > ( ) ,
2409+ & graph. units ,
2410+ & keys,
2411+ )
2412+ }
2413+
22252414/// One `{ name; binary; }` per unique test target across every root set.
22262415/// The template feeds this into a single manifest derivation so test
22272416/// enumeration is one IFD instead of one per binary.
@@ -2261,6 +2450,46 @@ fn render_test_target_entries(graph: &UnitGraph, prepared: &PreparedGraph) -> St
22612450 entries
22622451}
22632452
2453+ fn render_doctest_target_entries ( graph : & UnitGraph , prepared : & PreparedGraph ) -> Result < String > {
2454+ let keys = compute_doctest_keys ( graph, prepared) ;
2455+ let mut by_key: BTreeMap < String , String > = BTreeMap :: new ( ) ;
2456+ for ( & index, key) in & keys {
2457+ if by_key. contains_key ( key) {
2458+ continue ;
2459+ }
2460+ by_key. insert (
2461+ key. clone ( ) ,
2462+ format ! (
2463+ "{{ name = {}; listCommand = {}; allCommand = {}; runCommand = {}; }}" ,
2464+ nix_attr( key) ,
2465+ nix_indented_string( & render_doctest_command(
2466+ graph,
2467+ prepared,
2468+ index,
2469+ DoctestCommandMode :: List ,
2470+ ) ?) ,
2471+ nix_indented_string( & render_doctest_command(
2472+ graph,
2473+ prepared,
2474+ index,
2475+ DoctestCommandMode :: RunAll ,
2476+ ) ?) ,
2477+ nix_indented_string( & render_doctest_command(
2478+ graph,
2479+ prepared,
2480+ index,
2481+ DoctestCommandMode :: RunCase ,
2482+ ) ?) ,
2483+ ) ,
2484+ ) ;
2485+ }
2486+ let mut entries = String :: new ( ) ;
2487+ for ( _key, target) in by_key {
2488+ let _ = writeln ! ( entries, " {target}" ) ;
2489+ }
2490+ Ok ( entries)
2491+ }
2492+
22642493/// One `{ name; binary; }` per unique benchmark target across every root set.
22652494/// The template feeds this into benchmark plans and previous-vs-next Tango
22662495/// comparisons without another Cargo metadata pass.
0 commit comments