Skip to content

Commit 25a3fb3

Browse files
committed
extern_args module to get proper compiler args from Cargo.
1 parent 59e6afc commit 25a3fb3

File tree

3 files changed

+147
-1
lines changed

3 files changed

+147
-1
lines changed

src/book/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderConte
3131
use crate::utils;
3232

3333
use crate::config::{Config, RustEdition};
34+
use crate::utils::extern_args::ExternArgs;
3435

3536
/// The object used to manage and build a book.
3637
pub struct MDBook {
@@ -304,6 +305,14 @@ impl MDBook {
304305
let (book, _) = self.preprocess_book(&TestRenderer)?;
305306

306307
let color_output = std::io::stderr().is_terminal();
308+
309+
// get extra args we'll need for rustdoc
310+
// assumes current working directory is project root, eventually
311+
// pick up manifest directory from some config.
312+
313+
let mut extern_args = ExternArgs::new();
314+
extern_args.load(&std::env::current_dir()?)?;
315+
307316
let mut failed = false;
308317
for item in book.iter() {
309318
if let BookItem::Chapter(ref ch) = *item {
@@ -332,7 +341,8 @@ impl MDBook {
332341
cmd.current_dir(temp_dir.path())
333342
.arg(chapter_path)
334343
.arg("--test")
335-
.args(&library_args);
344+
.args(&library_args) // also need --extern for doctest to actually work
345+
.args(extern_args.get_args());
336346

337347
if let Some(edition) = self.config.rust.edition {
338348
match edition {

src/utils/extern_args.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//! Get "compiler" args from cargo
2+
3+
use crate::errors::*;
4+
use log::info;
5+
use std::fs::File;
6+
use std::path::{Path, PathBuf};
7+
use std::process::Command;
8+
9+
/// Get the arguments needed to invoke rustc so it can find external crates
10+
/// when invoked by rustdoc to compile doctests.
11+
///
12+
/// It seems the `-L <libraryPath>` and `--extern <lib>=<pathInDeps>` args are sufficient.
13+
///
14+
/// Cargo doesn't expose a stable API to get this information.
15+
/// `cargo metadata` does not include the hash suffix in `<pathInDeps>`.
16+
/// But it does leak when doing a build in verbose mode.
17+
/// So we force a cargo build, capture the console output and parse the args therefrom.
18+
///
19+
/// Example:
20+
/// ```rust
21+
///
22+
/// use mdbook::utils::extern_args::ExternArgs;
23+
/// # use mdbook::errors::*;
24+
///
25+
/// # fn main() -> Result<()> {
26+
/// // Get cargo to say what the compiler args need to be...
27+
/// let proj_root = std::env::current_dir()?; // or other path to `Cargo.toml`
28+
/// let mut extern_args = ExternArgs::new();
29+
/// extern_args.load(&proj_root)?;
30+
///
31+
/// // then, when actually invoking rustdoc or some other compiler-like tool...
32+
///
33+
/// assert!(extern_args.get_args().iter().any(|e| e == "-L")); // args contains "-L".to_string()
34+
/// assert!(extern_args.get_args().iter().any(|e| e == "--extern"));
35+
/// # Ok(())
36+
/// # }
37+
/// ```
38+
39+
#[derive(Debug)]
40+
pub struct ExternArgs {
41+
suffix_args: Vec<String>,
42+
}
43+
44+
impl ExternArgs {
45+
/// simple constructor
46+
pub fn new() -> Self {
47+
ExternArgs {
48+
suffix_args: vec![],
49+
}
50+
}
51+
52+
/// Run a `cargo build` to see what args Cargo is using for library paths and extern crates.
53+
/// Touch a source file to ensure something is compiled and the args will be visible.
54+
///
55+
/// >>>Future research: see whether `cargo check` can be used instead. It emits the `--extern`s
56+
/// with `.rmeta` instead of `.rlib`, and the compiler can't actually use those
57+
/// when compiling a doctest. But perhaps simply changing the file extension would work?
58+
pub fn load(&mut self, proj_root: &Path) -> Result<&Self> {
59+
// touch (change) a file in the project to force check to do something
60+
61+
for fname in ["lib.rs", "main.rs"] {
62+
let try_path: PathBuf = [&proj_root.to_string_lossy(), "src", fname]
63+
.iter()
64+
.collect();
65+
let f = File::options().append(true).open(&try_path)?;
66+
f.set_modified(std::time::SystemTime::now())?;
67+
break;
68+
// file should be closed when f goes out of scope at bottom of this loop
69+
}
70+
71+
let mut cmd = Command::new("cargo");
72+
cmd.current_dir(&proj_root).arg("build").arg("--verbose");
73+
74+
info!("running {:?}", cmd);
75+
let output = cmd.output()?;
76+
77+
if !output.status.success() {
78+
bail!("Exit status {} from {:?}", output.status, cmd);
79+
}
80+
81+
let cmd_resp: &str = std::str::from_utf8(&output.stderr)?;
82+
self.parse_response(&cmd_resp)?;
83+
84+
Ok(self)
85+
}
86+
87+
/// Parse response stdout+stderr response from `cargo build`
88+
/// into arguments we can use to invoke rustdoc.
89+
///
90+
/// >>> This parser is broken, doesn't handle arg values with embedded spaces (single quoted).
91+
/// Fortunately, the args we care about (so far) don't have those kinds of values.
92+
pub fn parse_response(&mut self, buf: &str) -> Result<()> {
93+
for l in buf.lines() {
94+
if let Some(_i) = l.find(" Running ") {
95+
let args_seg: &str = l.split('`').skip(1).take(1).collect::<Vec<_>>()[0]; // sadly, cargo decorates string with backticks
96+
let mut arg_iter = args_seg.split_whitespace();
97+
98+
while let Some(arg) = arg_iter.next() {
99+
match arg {
100+
"-L" | "--library-path" | "--extern" => {
101+
self.suffix_args.push(arg.to_owned());
102+
self.suffix_args
103+
.push(arg_iter.next().unwrap_or("").to_owned());
104+
}
105+
_ => {}
106+
}
107+
}
108+
};
109+
}
110+
111+
Ok(())
112+
}
113+
114+
/// get a list of (-L and --extern) args used to invoke rustdoc.
115+
pub fn get_args(&self) -> Vec<String> {
116+
self.suffix_args.clone()
117+
}
118+
}
119+
120+
#[cfg(test)]
121+
mod test {
122+
use super::*;
123+
124+
#[test]
125+
fn parse_response_parses_string() -> Result<()> {
126+
let resp = std::fs::read_to_string("tests/t1.txt")?;
127+
let mut ea = ExternArgs::new();
128+
ea.parse_response(&resp)?;
129+
130+
let sfx = ea.get_args();
131+
assert!(sfx.len() > 0);
132+
133+
Ok(())
134+
}
135+
}

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod fs;
44
mod string;
55
pub(crate) mod toml_ext;
6+
pub mod extern_args;
67
use crate::errors::Error;
78
use log::error;
89
use once_cell::sync::Lazy;

0 commit comments

Comments
 (0)