@@ -47,6 +47,7 @@ struct State<'a, 'cfg> {
4747 target_data : & ' a RustcTargetData < ' cfg > ,
4848 profiles : & ' a Profiles ,
4949 interner : & ' a UnitInterner ,
50+ scrape_units : & ' a [ Unit ] ,
5051
5152 /// A set of edges in `unit_dependencies` where (a, b) means that the
5253 /// dependency from a to b was added purely because it was a dev-dependency.
@@ -61,6 +62,7 @@ pub fn build_unit_dependencies<'a, 'cfg>(
6162 features : & ' a ResolvedFeatures ,
6263 std_resolve : Option < & ' a ( Resolve , ResolvedFeatures ) > ,
6364 roots : & [ Unit ] ,
65+ scrape_units : & [ Unit ] ,
6466 std_roots : & HashMap < CompileKind , Vec < Unit > > ,
6567 global_mode : CompileMode ,
6668 target_data : & ' a RustcTargetData < ' cfg > ,
@@ -91,6 +93,7 @@ pub fn build_unit_dependencies<'a, 'cfg>(
9193 target_data,
9294 profiles,
9395 interner,
96+ scrape_units,
9497 dev_dependency_edges : HashSet :: new ( ) ,
9598 } ;
9699
@@ -253,6 +256,7 @@ fn compute_deps(
253256 if !dep. is_transitive ( )
254257 && !unit. target . is_test ( )
255258 && !unit. target . is_example ( )
259+ && !unit. mode . is_doc_scrape ( )
256260 && !unit. mode . is_any_test ( )
257261 {
258262 return false ;
@@ -467,6 +471,25 @@ fn compute_deps_doc(
467471 if unit. target . is_bin ( ) || unit. target . is_example ( ) {
468472 ret. extend ( maybe_lib ( unit, state, unit_for) ?) ;
469473 }
474+
475+ // Add all units being scraped for examples as a dependency of Doc units.
476+ if state. ws . is_member ( & unit. pkg ) {
477+ for scrape_unit in state. scrape_units . iter ( ) {
478+ // This needs to match the FeaturesFor used in cargo_compile::generate_targets.
479+ let unit_for = UnitFor :: new_host ( scrape_unit. target . proc_macro ( ) ) ;
480+ deps_of ( scrape_unit, state, unit_for) ?;
481+ ret. push ( new_unit_dep (
482+ state,
483+ scrape_unit,
484+ & scrape_unit. pkg ,
485+ & scrape_unit. target ,
486+ unit_for,
487+ scrape_unit. kind ,
488+ scrape_unit. mode ,
489+ ) ?) ;
490+ }
491+ }
492+
470493 Ok ( ret)
471494}
472495
@@ -558,7 +581,7 @@ fn dep_build_script(
558581/// Choose the correct mode for dependencies.
559582fn check_or_build_mode ( mode : CompileMode , target : & Target ) -> CompileMode {
560583 match mode {
561- CompileMode :: Check { .. } | CompileMode :: Doc { .. } => {
584+ CompileMode :: Check { .. } | CompileMode :: Doc { .. } | CompileMode :: Docscrape => {
562585 if target. for_host ( ) {
563586 // Plugin and proc macro targets should be compiled like
564587 // normal.
@@ -695,6 +718,14 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_>) {
695718 && other. unit . target . is_linkable ( )
696719 && other. unit . pkg . manifest ( ) . links ( ) . is_some ( )
697720 } )
721+ // Avoid cycles when using the doc --scrape-examples feature:
722+ // Say a workspace has crates A and B where A has a build-dependency on B.
723+ // The Doc units for A and B will have a dependency on the Docscrape for both A and B.
724+ // So this would add a dependency from B-build to A-build, causing a cycle:
725+ // B (build) -> A (build) -> B(build)
726+ // See the test scrape_examples_avoid_build_script_cycle for a concrete example.
727+ // To avoid this cycle, we filter out the B -> A (docscrape) dependency.
728+ . filter ( |( _parent, other) | !other. unit . mode . is_doc_scrape ( ) )
698729 // Skip dependencies induced via dev-dependencies since
699730 // connections between `links` and build scripts only happens
700731 // via normal dependencies. Otherwise since dev-dependencies can
0 commit comments