Skip to content

Commit cb77a89

Browse files
committed
When running build, pull dependencies from the build of the doctest crate
1 parent 6104051 commit cb77a89

File tree

3 files changed

+126
-53
lines changed

3 files changed

+126
-53
lines changed

guide/book.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ authors = ["Mathieu David", "Michael-F-Bryan"]
55
language = "en"
66

77
[rust]
8-
edition = "2018"
9-
package-dir = "../"
8+
## not needed, and will cause an error, if using Cargo.toml: edition = "2021"
9+
package-dir = "."
1010

1111
[output.html]
1212
smart-punctuation = true

src/book/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl MDBook {
237237
.chapter_titles
238238
.extend(preprocess_ctx.chapter_titles.borrow_mut().drain());
239239

240-
info!("Running the {} backend", renderer.name());
240+
debug!("Running the {} backend", renderer.name());
241241
renderer
242242
.render(&render_context)
243243
.with_context(|| "Rendering failed")

src/utils/extern_args.rs

Lines changed: 123 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Get "compiler" args from cargo
22
33
use crate::errors::*;
4-
use log::{info, warn};
4+
use cargo_manifest::{Edition, Manifest, MaybeInherited::Local};
5+
use log::{debug, info};
56
use std::fs;
67
use std::fs::File;
78
use std::io::prelude::*;
@@ -40,45 +41,73 @@ use std::process::Command;
4041
4142
#[derive(Debug)]
4243
pub struct ExternArgs {
43-
suffix_args: Vec<String>,
44+
edition: String,
45+
crate_name: String,
46+
lib_list: Vec<String>,
47+
extern_list: Vec<String>,
4448
}
4549

4650
impl ExternArgs {
4751
/// simple constructor
4852
pub fn new() -> Self {
4953
ExternArgs {
50-
suffix_args: vec![],
54+
edition: String::default(),
55+
crate_name: String::default(),
56+
lib_list: vec![],
57+
extern_list: vec![],
5158
}
5259
}
5360

5461
/// Run a `cargo build` to see what args Cargo is using for library paths and extern crates.
55-
/// Touch a source file to ensure something is compiled and the args will be visible.
56-
///
57-
/// >>>Future research: see whether `cargo check` can be used instead. It emits the `--extern`s
58-
/// with `.rmeta` instead of `.rlib`, and the compiler can't actually use those
59-
/// when compiling a doctest. But perhaps simply changing the file extension would work?
62+
/// Touch a source file in the crate to ensure something is compiled and the args will be visible.
63+
6064
pub fn load(&mut self, proj_root: &Path) -> Result<&Self> {
65+
// find Cargo.toml and determine the package name and lib or bin source file.
66+
let cargo_path = proj_root.join("Cargo.toml");
67+
let mut manifest = Manifest::from_path(&cargo_path)?;
68+
manifest.complete_from_path(proj_root)?; // try real hard to determine bin or lib
69+
let package = manifest
70+
.package
71+
.expect("doctest Cargo.toml must include a [package] section");
72+
73+
self.crate_name = package.name.replace('-', "_"); // maybe cargo shouldn't allow packages to include non-identifier characters?
74+
// in any case, this won't work when default crate doesn't have package name (which I'm sure cargo allows somehow or another)
75+
self.edition = if let Some(Local(edition)) = package.edition {
76+
my_display_edition(edition)
77+
} else {
78+
"2015".to_owned() // and good luck to you, sir!
79+
};
80+
81+
debug!(
82+
"parsed from manifest: name: {}, edition: {}",
83+
self.crate_name,
84+
format!("{:?}", self.edition)
85+
);
86+
6187
// touch (change) a file in the project to force check to do something
88+
// I haven't figured out how to determine bin or lib source file from cargo, fall back on heuristics here.
6289

63-
for fname in ["lib.rs", "main.rs"] {
64-
let try_path: PathBuf = [&proj_root.to_string_lossy(), "src", fname]
65-
.iter()
66-
.collect();
90+
for fname in ["main.rs", "lib.rs"] {
91+
let try_path: PathBuf = proj_root.join("src").join(fname);
6792
if try_path.exists() {
6893
touch(&try_path)?;
69-
self.run_cargo(proj_root)?;
94+
self.run_cargo(proj_root, &cargo_path)?;
7095
return Ok(self);
7196
// file should be closed when f goes out of scope at bottom of this loop
7297
}
7398
}
74-
bail!("Couldn't find source target in project {:?}", proj_root)
99+
bail!("Couldn't find lib or bin source in project {:?}", proj_root)
75100
}
76101

77-
fn run_cargo(&mut self, proj_root: &Path) -> Result<&Self> {
102+
fn run_cargo(&mut self, proj_root: &Path, manifest_path: &Path) -> Result<&Self> {
78103
let mut cmd = Command::new("cargo");
79-
cmd.current_dir(&proj_root).arg("build").arg("--verbose");
80-
104+
cmd.current_dir(&proj_root)
105+
.arg("build")
106+
.arg("--verbose")
107+
.arg("--manifest-path")
108+
.arg(manifest_path);
81109
info!("running {:?}", cmd);
110+
82111
let output = cmd.output()?;
83112

84113
if !output.status.success() {
@@ -90,63 +119,107 @@ impl ExternArgs {
90119
);
91120
}
92121

122+
//ultimatedebug std::fs::write(proj_root.join("mdbook_cargo_out.txt"), &output.stderr)?;
123+
93124
let cmd_resp: &str = std::str::from_utf8(&output.stderr)?;
94-
self.parse_response(&cmd_resp)?;
125+
self.parse_response(&self.crate_name.clone(), &cmd_resp)?;
95126

96127
Ok(self)
97128
}
98129

99130
/// Parse response stdout+stderr response from `cargo build`
100-
/// into arguments we can use to invoke rustdoc.
101-
/// Stop at first line that traces a compiler invocation.
131+
/// into arguments we can use to invoke rustdoc (--edition --extern and -L).
132+
/// The response may contain multiple builds, scan for the one that corresponds to the doctest crate.
102133
///
103-
/// >>> This parser is broken, doesn't handle arg values with embedded spaces (single quoted).
134+
/// > This parser is broken, doesn't handle arg values with embedded spaces (single quoted).
104135
/// Fortunately, the args we care about (so far) don't have those kinds of values.
105-
pub fn parse_response(&mut self, buf: &str) -> Result<()> {
136+
pub fn parse_response(&mut self, my_crate: &str, buf: &str) -> Result<()> {
137+
let mut builds_ignored = 0;
138+
139+
let my_cn_arg = format!(" --crate-name {}", my_crate);
106140
for l in buf.lines() {
107141
if let Some(_i) = l.find(" Running ") {
108-
let args_seg: &str = l.split('`').skip(1).take(1).collect::<Vec<_>>()[0]; // sadly, cargo decorates string with backticks
109-
let mut arg_iter = args_seg.split_whitespace();
110-
111-
while let Some(arg) = arg_iter.next() {
112-
match arg {
113-
"-L" | "--library-path" => {
114-
self.suffix_args.push(arg.to_owned());
115-
self.suffix_args
116-
.push(arg_iter.next().unwrap_or("").to_owned());
142+
if let Some(_cn_pos) = l.find(&my_cn_arg) {
143+
let args_seg: &str = l.split('`').skip(1).take(1).collect::<Vec<_>>()[0]; // sadly, cargo decorates string with backticks
144+
let mut arg_iter = args_seg.split_whitespace();
145+
146+
while let Some(arg) = arg_iter.next() {
147+
match arg {
148+
"-L" | "--library-path" => {
149+
self.lib_list
150+
.push(arg_iter.next().unwrap_or_default().to_owned());
151+
}
152+
153+
"--extern" => {
154+
let mut dep_arg = arg_iter.next().unwrap_or_default().to_owned();
155+
156+
// sometimes, build references the.rmeta even though our doctests will require .rlib
157+
// so convert the argument and hope for the best.
158+
// if .rlib is not there when the doctest runs, it will complain.
159+
if dep_arg.ends_with(".rmeta") {
160+
debug!(
161+
"Build referenced {}, converted to .rlib hoping that actual file will be there in time.",
162+
dep_arg);
163+
dep_arg = dep_arg.replace(".rmeta", ".rlib");
164+
}
165+
self.extern_list.push(dep_arg);
166+
}
167+
168+
"--crate-name" => {
169+
self.crate_name = arg_iter.next().unwrap_or_default().to_owned();
170+
}
171+
172+
_ => {
173+
if let Some((kw, val)) = arg.split_once('=') {
174+
if kw == "--edition" {
175+
self.edition = val.to_owned();
176+
}
177+
}
178+
}
117179
}
118-
"--extern" => {
119-
// needs a hack to force reference to rlib over rmeta
120-
self.suffix_args.push(arg.to_owned());
121-
self.suffix_args.push(
122-
arg_iter
123-
.next()
124-
.unwrap_or("")
125-
.replace(".rmeta", ".rlib")
126-
.to_owned(),
127-
);
128-
}
129-
_ => {}
130180
}
181+
} else {
182+
builds_ignored += 1;
131183
}
132-
133-
return Ok(());
134184
};
135185
}
136186

137-
if self.suffix_args.len() < 1 {
138-
warn!("Couldn't extract --extern args from Cargo, is current directory == cargo project root?");
187+
if self.extern_list.len() == 0 || self.lib_list.len() == 0 {
188+
bail!("Couldn't extract -L or --extern args from Cargo, is current directory == cargo project root?");
139189
}
140190

191+
debug!(
192+
"Ignored {} other builds performed in this run",
193+
builds_ignored
194+
);
195+
141196
Ok(())
142197
}
143198

144-
/// get a list of (-L and --extern) args used to invoke rustdoc.
199+
/// provide the parsed external args used to invoke rustdoc (--edition, -L and --extern).
145200
pub fn get_args(&self) -> Vec<String> {
146-
self.suffix_args.clone()
201+
let mut ret_val: Vec<String> = vec!["--edition".to_owned(), self.edition.clone()];
202+
for i in &self.lib_list {
203+
ret_val.push("-L".to_owned());
204+
ret_val.push(i.clone());
205+
}
206+
for j in &self.extern_list {
207+
ret_val.push("--extern".to_owned());
208+
ret_val.push(j.clone());
209+
}
210+
ret_val
147211
}
148212
}
149213

214+
fn my_display_edition(edition: Edition) -> String {
215+
match edition {
216+
Edition::E2015 => "2015",
217+
Edition::E2018 => "2018",
218+
Edition::E2021 => "2021",
219+
Edition::E2024 => "2024",
220+
}
221+
.to_owned()
222+
}
150223
// Private "touch" function to update file modification time without changing content.
151224
// needed because [std::fs::set_modified] is unstable in rust 1.74,
152225
// which is currently the MSRV for mdBook. It is available in rust 1.76 onward.
@@ -196,7 +269,7 @@ mod test {
196269
"###;
197270

198271
let mut ea = ExternArgs::new();
199-
ea.parse_response(&test_str)?;
272+
ea.parse_response(&test_str, "leptos_book")?;
200273

201274
let args = ea.get_args();
202275
assert_eq!(18, args.len());

0 commit comments

Comments
 (0)