Skip to content

Commit 7bccfc3

Browse files
committed
Support doctests
1 parent 6a1697c commit 7bccfc3

File tree

8 files changed

+169
-53
lines changed

8 files changed

+169
-53
lines changed

src/bin/cargo/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Available unstable (nightly-only) flags:
4646
-Z terminal-width -- Provide a terminal width to rustc for error truncation
4747
-Z namespaced-features -- Allow features with `dep:` prefix
4848
-Z weak-dep-features -- Allow `dep_name?/feature` feature syntax
49+
-Z warn-unused-deps -- Emit warnings about unused dependencies
4950
-Z patch-in-config -- Allow `[patch]` sections in .cargo/config.toml files
5051
5152
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"

src/cargo/core/compiler/compilation.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use cargo_platform::CfgExpr;
77
use cargo_util::{paths, ProcessBuilder};
88
use semver::Version;
99

10-
use super::BuildContext;
10+
use super::unused_dependencies::UnusedDepState;
11+
use super::{BuildContext, UnitDep};
1112
use crate::core::compiler::{CompileKind, Metadata, Unit};
1213
use crate::core::Package;
1314
use crate::util::{config, CargoResult, Config};
@@ -16,6 +17,8 @@ use crate::util::{config, CargoResult, Config};
1617
pub struct Doctest {
1718
/// What's being doctested
1819
pub unit: Unit,
20+
/// Dependencies of the unit
21+
pub unit_deps: Vec<UnitDep>,
1922
/// Arguments needed to pass to rustdoc to run this test.
2023
pub args: Vec<OsString>,
2124
/// Whether or not -Zunstable-options is needed.
@@ -86,6 +89,8 @@ pub struct Compilation<'cfg> {
8689
/// The target host triple.
8790
pub host: String,
8891

92+
pub(crate) unused_dep_state: Option<UnusedDepState>,
93+
8994
config: &'cfg Config,
9095

9196
/// Rustc process to be used by default
@@ -141,6 +146,7 @@ impl<'cfg> Compilation<'cfg> {
141146
to_doc_test: Vec::new(),
142147
config: bcx.config,
143148
host: bcx.host_triple().to_string(),
149+
unused_dep_state: None,
144150
rustc_process: rustc,
145151
rustc_workspace_wrapper_process,
146152
primary_rustc_process,

src/cargo/core/compiler/context/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
168168
}
169169

170170
// Now that we've figured out everything that we're going to do, do it!
171-
queue.execute(&mut self, &mut plan)?;
171+
let unused_dep_state = queue.execute(&mut self, &mut plan)?;
172+
173+
self.compilation.unused_dep_state = Some(unused_dep_state);
172174

173175
if build_plan {
174176
plan.set_inputs(self.build_plan_inputs()?);
@@ -255,6 +257,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
255257

256258
self.compilation.to_doc_test.push(compilation::Doctest {
257259
unit: unit.clone(),
260+
unit_deps: self.unit_deps(&unit).to_vec(),
258261
args,
259262
unstable_opts,
260263
linker: self.bcx.linker(unit.kind),

src/cargo/core/compiler/job_queue.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,11 @@ impl<'cfg> JobQueue<'cfg> {
415415
/// This function will spawn off `config.jobs()` workers to build all of the
416416
/// necessary dependencies, in order. Freshness is propagated as far as
417417
/// possible along each dependency chain.
418-
pub fn execute(mut self, cx: &mut Context<'_, '_>, plan: &mut BuildPlan) -> CargoResult<()> {
418+
pub fn execute(
419+
mut self,
420+
cx: &mut Context<'_, '_>,
421+
plan: &mut BuildPlan,
422+
) -> CargoResult<UnusedDepState> {
419423
let _p = profile::start("executing the job graph");
420424
self.queue.queue_finished();
421425

@@ -435,7 +439,7 @@ impl<'cfg> JobQueue<'cfg> {
435439
progress,
436440
next_id: 0,
437441
timings: self.timings,
438-
unused_dep_state: UnusedDepState::new_with_graph(cx), // TODO
442+
unused_dep_state: UnusedDepState::new_with_graph(cx),
439443
tokens: Vec::new(),
440444
rustc_tokens: HashMap::new(),
441445
to_send_clients: BTreeMap::new(),
@@ -629,8 +633,12 @@ impl<'cfg> DrainState<'cfg> {
629633
}
630634
Message::UnusedExterns(id, unused_externs) => {
631635
let unit = &self.active[&id];
632-
self.unused_dep_state
633-
.record_unused_externs_for_unit(cx, unit, unused_externs);
636+
let unit_deps = cx.unit_deps(&unit);
637+
self.unused_dep_state.record_unused_externs_for_unit(
638+
unit_deps,
639+
unit,
640+
unused_externs,
641+
);
634642
}
635643
Message::Token(acquired_token) => {
636644
let token = acquired_token.chain_err(|| "failed to acquire jobserver token")?;
@@ -716,7 +724,7 @@ impl<'cfg> DrainState<'cfg> {
716724
plan: &mut BuildPlan,
717725
scope: &Scope<'_>,
718726
jobserver_helper: &HelperThread,
719-
) -> (Result<(), anyhow::Error>,) {
727+
) -> (Result<UnusedDepState, anyhow::Error>,) {
720728
trace!("queue: {:#?}", self.queue);
721729

722730
// Iteratively execute the entire dependency graph. Each turn of the
@@ -805,7 +813,7 @@ impl<'cfg> DrainState<'cfg> {
805813
}
806814

807815
if !cx.bcx.build_config.build_plan && cx.bcx.config.cli_unstable().warn_unused_deps {
808-
drop(self.unused_dep_state.emit_unused_warnings(cx));
816+
drop(self.unused_dep_state.emit_unused_early_warnings(cx));
809817
}
810818

811819
if let Some(e) = error {
@@ -821,7 +829,7 @@ impl<'cfg> DrainState<'cfg> {
821829
self.emit_future_incompat(cx);
822830
}
823831

824-
(Ok(()),)
832+
(Ok(self.unused_dep_state),)
825833
} else {
826834
debug!("queue: {:#?}", self.queue);
827835
(Err(internal("finished with jobs still left in the queue")),)

src/cargo/core/compiler/unused_dependencies.rs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
use super::unit::Unit;
2-
use super::Context;
2+
use super::{Context, UnitDep};
33
use crate::core::compiler::build_config::CompileMode;
44
use crate::core::dependency::DepKind;
55
use crate::core::manifest::TargetKind;
66
use crate::core::Dependency;
77
use crate::core::PackageId;
88
use crate::util::errors::CargoResult;
99
use crate::util::interning::InternedString;
10+
use crate::Config;
1011
use log::trace;
1112

1213
use std::collections::{HashMap, HashSet};
1314

1415
pub type AllowedKinds = HashSet<DepKind>;
1516

16-
#[derive(Default)]
17+
#[derive(Default, Clone)]
1718
struct State {
1819
/// All externs of a root unit.
1920
externs: HashMap<InternedString, Option<Dependency>>,
@@ -24,6 +25,7 @@ struct State {
2425
reports_needed_by: HashSet<Unit>,
2526
}
2627

28+
#[derive(Clone)]
2729
pub struct UnusedDepState {
2830
states: HashMap<(PackageId, Option<DepKind>), State>,
2931
/// Tracking for which units we have received reports from.
@@ -51,6 +53,8 @@ fn dep_kind_of(unit: &Unit) -> DepKind {
5153
TargetKind::Lib(_) => match unit.mode {
5254
// To support lib.rs with #[cfg(test)] use foo_crate as _;
5355
CompileMode::Test => DepKind::Development,
56+
// To correctly register dev-dependencies
57+
CompileMode::Doctest => DepKind::Development,
5458
_ => DepKind::Normal,
5559
},
5660
TargetKind::Bin => DepKind::Normal,
@@ -120,10 +124,9 @@ impl UnusedDepState {
120124
root.pkg.name(),
121125
unit_desc(root),
122126
);
123-
// We aren't getting output from doctests, so skip them (for now)
124127
if root.mode == CompileMode::Doctest {
125-
trace!(" -> skipping doctest");
126-
continue;
128+
//trace!(" -> skipping doctest");
129+
//continue;
127130
}
128131
for dep in cx.unit_deps(root).iter() {
129132
trace!(
@@ -156,13 +159,12 @@ impl UnusedDepState {
156159
/// and then updating the global list of used externs
157160
pub fn record_unused_externs_for_unit(
158161
&mut self,
159-
cx: &mut Context<'_, '_>,
162+
unit_deps: &[UnitDep],
160163
unit: &Unit,
161164
unused_externs: Vec<String>,
162165
) {
163166
self.reports_obtained.insert(unit.clone());
164-
let usable_deps_iter = cx
165-
.unit_deps(unit)
167+
let usable_deps_iter = unit_deps
166168
.iter()
167169
// compare with similar check in extern_args
168170
.filter(|dep| dep.unit.target.is_linkable() && !dep.unit.mode.is_doc());
@@ -194,18 +196,28 @@ impl UnusedDepState {
194196
let record_kind = dep_kind_of(unit);
195197
trace!(
196198
" => updating state of {}dep",
197-
dep_kind_desc(Some(record_kind))
199+
dep_kind_desc(Some(record_kind)),
198200
);
199201
state
200202
.used_externs
201203
.insert((used_dep.extern_crate_name, record_kind));
202204
}
203205
}
204206
}
205-
pub fn emit_unused_warnings(&self, cx: &mut Context<'_, '_>) -> CargoResult<()> {
207+
pub fn emit_unused_early_warnings(&self, cx: &mut Context<'_, '_>) -> CargoResult<()> {
208+
self.emit_unused_warnings_inner(cx.bcx.config, Some(&cx.bcx.allowed_kinds))
209+
}
210+
pub fn emit_unused_late_warnings(&self, config: &Config) -> CargoResult<()> {
211+
self.emit_unused_warnings_inner(config, None)
212+
}
213+
fn emit_unused_warnings_inner(
214+
&self,
215+
config: &Config,
216+
allowed_kinds_or_late: Option<&AllowedKinds>,
217+
) -> CargoResult<()> {
206218
trace!(
207219
"Allowed dependency kinds for the unused deps check: {:?}",
208-
cx.bcx.allowed_kinds
220+
allowed_kinds_or_late
209221
);
210222

211223
// Sort the states to have a consistent output
@@ -247,12 +259,15 @@ impl UnusedDepState {
247259
} else {
248260
continue;
249261
};
250-
if !cx.bcx.allowed_kinds.contains(dep_kind) {
251-
// We can't warn for dependencies of this target kind
252-
// as we aren't compiling all the units
253-
// that use the dependency kind
254-
trace!("Supressing unused deps warning of {} in pkg {} v{} as mode '{}dep' not allowed", dependency.name_in_toml(), pkg_id.name(), pkg_id.version(), dep_kind_desc(Some(*dep_kind)));
255-
continue;
262+
if let Some(allowed_kinds) = allowed_kinds_or_late {
263+
if !allowed_kinds.contains(dep_kind) {
264+
// We can't warn for dependencies of this target kind
265+
// as we aren't compiling all the units
266+
// that use the dependency kind
267+
trace!("Supressing unused deps warning of {} in pkg {} v{} as mode '{}dep' not allowed", dependency.name_in_toml(), pkg_id.name(), pkg_id.version(), dep_kind_desc(Some(*dep_kind)));
268+
continue;
269+
}
270+
} else {
256271
}
257272
if dependency.name_in_toml().starts_with("_") {
258273
// Dependencies starting with an underscore
@@ -270,7 +285,7 @@ impl UnusedDepState {
270285
{
271286
// The dependency is used but only by dev targets,
272287
// which means it should be a dev-dependency instead
273-
cx.bcx.config.shell().warn(format!(
288+
config.shell().warn(format!(
274289
"dependency {} in package {} v{} is only used by dev targets",
275290
dependency.name_in_toml(),
276291
pkg_id.name(),
@@ -279,7 +294,7 @@ impl UnusedDepState {
279294
continue;
280295
}
281296

282-
cx.bcx.config.shell().warn(format!(
297+
config.shell().warn(format!(
283298
"unused {}dependency {} in package {} v{}",
284299
dep_kind_desc(Some(*dep_kind)),
285300
dependency.name_in_toml(),

src/cargo/ops/cargo_compile.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,6 @@ fn generate_targets(
975975
}
976976
}
977977
allowed_kinds.insert(DepKind::Normal);
978-
allowed_kinds.insert(DepKind::Development);
979978
}
980979
CompileFilter::Only {
981980
all_targets,
@@ -1036,12 +1035,6 @@ fn generate_targets(
10361035
};
10371036

10381037
if *lib != LibRule::False {
1039-
match (bins, examples, tests, benches) {
1040-
(FilterRule::All, FilterRule::All, FilterRule::All, FilterRule::All) => {
1041-
allowed_kinds.insert(DepKind::Development);
1042-
}
1043-
_ => (),
1044-
}
10451038
match (bins, examples, tests, benches) {
10461039
(FilterRule::All, ..) => {
10471040
allowed_kinds.insert(DepKind::Normal);

src/cargo/ops/cargo_test.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ fn run_doc_tests(
168168
args,
169169
unstable_opts,
170170
unit,
171+
unit_deps,
171172
linker,
172173
script_meta,
173174
} = doctest_info;
@@ -228,6 +229,12 @@ fn run_doc_tests(
228229
p.arg("-L").arg(arg);
229230
}
230231

232+
if config.cli_unstable().warn_unused_deps {
233+
p.arg("-Z").arg("unstable-options");
234+
p.arg("--error-format=json");
235+
p.arg("--json=unused-externs");
236+
}
237+
231238
for native_dep in compilation.native_dirs.iter() {
232239
p.arg("-L").arg(native_dep);
233240
}
@@ -245,13 +252,43 @@ fn run_doc_tests(
245252
config
246253
.shell()
247254
.verbose(|shell| shell.status("Running", p.to_string()))?;
248-
if let Err(e) = p.exec() {
255+
256+
let mut unused_dep_state = compilation.unused_dep_state.clone().unwrap();
257+
if let Err(e) = p.exec_with_streaming(
258+
&mut |line| {
259+
writeln!(config.shell().out(), "{}", line)?;
260+
Ok(())
261+
},
262+
&mut |line| {
263+
#[derive(serde::Deserialize)]
264+
struct UnusedExterns {
265+
unused_extern_names: Vec<String>,
266+
}
267+
if let Ok(uext) = serde_json::from_str::<UnusedExterns>(line) {
268+
unused_dep_state.record_unused_externs_for_unit(
269+
&unit_deps,
270+
unit,
271+
uext.unused_extern_names,
272+
);
273+
// Supress output of the json formatted unused extern message
274+
return Ok(());
275+
}
276+
writeln!(config.shell().err(), "{}", line)?;
277+
Ok(())
278+
},
279+
false,
280+
) {
249281
let e = e.downcast::<ProcessError>()?;
250282
errors.push(e);
251283
if !options.no_fail_fast {
252284
return Ok((Test::Doc, errors));
253285
}
254286
}
287+
if config.cli_unstable().warn_unused_deps {
288+
// Emit unused dependencies report which has been held back
289+
// until now, as doctests could use things
290+
unused_dep_state.emit_unused_late_warnings(config)?;
291+
}
255292
}
256293
Ok((Test::Doc, errors))
257294
}

0 commit comments

Comments
 (0)