Skip to content

Commit b0316bc

Browse files
committed
auto merge of #254 : alexcrichton/cargo/cargo-doc, r=wycats
This is blocked until rust-lang/rust#15939 lands, but in the meantime I figured I could get some eyes on this to make sure I'm sane. Closes #130
2 parents d885f91 + 2c5af68 commit b0316bc

File tree

15 files changed

+408
-21
lines changed

15 files changed

+408
-21
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,9 @@ test = false
6767
name = "cargo-new"
6868
test = false
6969

70+
[[bin]]
71+
name = "cargo-doc"
72+
test = false
73+
7074
[[test]]
7175
name = "tests"

src/bin/cargo-doc.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#![feature(phase)]
2+
3+
#[phase(plugin, link)]
4+
extern crate cargo;
5+
extern crate serialize;
6+
7+
#[phase(plugin, link)]
8+
extern crate hammer;
9+
10+
use std::os;
11+
12+
use cargo::ops;
13+
use cargo::{execute_main_without_stdin};
14+
use cargo::core::{MultiShell};
15+
use cargo::util::{CliResult, CliError};
16+
use cargo::util::important_paths::find_project_manifest;
17+
18+
#[deriving(PartialEq,Clone,Decodable)]
19+
struct Options {
20+
manifest_path: Option<String>,
21+
jobs: Option<uint>,
22+
update: bool,
23+
no_deps: bool,
24+
}
25+
26+
hammer_config!(Options "Build the package's documentation", |c| {
27+
c.short("jobs", 'j').short("update", 'u')
28+
})
29+
30+
fn main() {
31+
execute_main_without_stdin(execute);
32+
}
33+
34+
fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
35+
let root = match options.manifest_path {
36+
Some(path) => Path::new(path),
37+
None => try!(find_project_manifest(&os::getcwd(), "Cargo.toml")
38+
.map_err(|_| {
39+
CliError::new("Could not find Cargo.toml in this \
40+
directory or any parent directory",
41+
102)
42+
}))
43+
};
44+
45+
let mut doc_opts = ops::DocOptions {
46+
all: !options.no_deps,
47+
compile_opts: ops::CompileOptions {
48+
update: options.update,
49+
env: if options.no_deps {"doc"} else {"doc-all"},
50+
shell: shell,
51+
jobs: options.jobs,
52+
target: None,
53+
},
54+
};
55+
56+
try!(ops::doc(&root, &mut doc_opts).map_err(|err| {
57+
CliError::from_boxed(err, 101)
58+
}));
59+
60+
Ok(None)
61+
}
62+

src/bin/cargo.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ fn execute() {
5757
println!(" run # build and execute src/main.rs");
5858
println!(" version # displays the version of cargo");
5959
println!(" new # create a new cargo project");
60+
println!(" doc # build project's rustdoc documentation");
6061
println!("");
6162

6263

src/cargo/core/manifest.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub struct Manifest {
1919
authors: Vec<String>,
2020
targets: Vec<Target>,
2121
target_dir: Path,
22+
doc_dir: Path,
2223
sources: Vec<SourceId>,
2324
build: Vec<String>,
2425
unused_keys: Vec<String>,
@@ -41,6 +42,7 @@ pub struct SerializedManifest {
4142
authors: Vec<String>,
4243
targets: Vec<Target>,
4344
target_dir: String,
45+
doc_dir: String,
4446
build: Option<Vec<String>>,
4547
}
4648

@@ -55,6 +57,7 @@ impl<E, S: Encoder<E>> Encodable<S, E> for Manifest {
5557
authors: self.authors.clone(),
5658
targets: self.targets.clone(),
5759
target_dir: self.target_dir.display().to_string(),
60+
doc_dir: self.doc_dir.display().to_string(),
5861
build: if self.build.len() == 0 { None } else { Some(self.build.clone()) },
5962
}.encode(s)
6063
}
@@ -155,10 +158,25 @@ impl Profile {
155158
}
156159
}
157160

161+
pub fn default_doc() -> Profile {
162+
Profile {
163+
env: "doc".to_string(),
164+
opt_level: 0,
165+
debug: false,
166+
test: false,
167+
dest: Some("doc-build".to_string()),
168+
plugin: false,
169+
}
170+
}
171+
158172
pub fn is_compile(&self) -> bool {
159173
self.env.as_slice() == "compile"
160174
}
161175

176+
pub fn is_doc(&self) -> bool {
177+
self.env.as_slice() == "doc"
178+
}
179+
162180
pub fn is_test(&self) -> bool {
163181
self.test
164182
}
@@ -249,13 +267,14 @@ impl Show for Target {
249267

250268
impl Manifest {
251269
pub fn new(summary: &Summary, targets: &[Target],
252-
target_dir: &Path, sources: Vec<SourceId>,
270+
target_dir: &Path, doc_dir: &Path, sources: Vec<SourceId>,
253271
build: Vec<String>) -> Manifest {
254272
Manifest {
255273
summary: summary.clone(),
256274
authors: Vec::new(),
257275
targets: Vec::from_slice(targets),
258276
target_dir: target_dir.clone(),
277+
doc_dir: doc_dir.clone(),
259278
sources: sources,
260279
build: build,
261280
unused_keys: Vec::new(),
@@ -294,6 +313,10 @@ impl Manifest {
294313
&self.target_dir
295314
}
296315

316+
pub fn get_doc_dir(&self) -> &Path {
317+
&self.doc_dir
318+
}
319+
297320
pub fn get_source_ids(&self) -> &[SourceId] {
298321
self.sources.as_slice()
299322
}

src/cargo/ops/cargo_clean.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
use std::io::fs::{rmdir_recursive};
2-
use core::{SourceId};
2+
3+
use core::source::Source;
4+
use sources::PathSource;
35
use util::{CargoResult, human, ChainError};
4-
use ops::{read_manifest};
5-
use std::io::{File};
6-
use util::toml::{project_layout};
76

87
/// Cleans the project from build artifacts.
98
109
pub fn clean(manifest_path: &Path) -> CargoResult<()> {
11-
let mut file = try!(File::open(manifest_path));
12-
let data = try!(file.read_to_end());
13-
let layout = project_layout(&manifest_path.dir_path());
14-
let (manifest, _) = try!(read_manifest(data.as_slice(),
15-
layout,
16-
&SourceId::for_path(manifest_path)));
10+
let mut src = PathSource::for_path(&manifest_path.dir_path());
11+
try!(src.update());
12+
let root = try!(src.get_root_package());
13+
let manifest = root.get_manifest();
1714

1815
let build_dir = manifest.get_target_dir();
19-
2016
if build_dir.exists() {
21-
rmdir_recursive(build_dir).chain_error(|| human("Could not remove build directory"))
22-
} else {
23-
Ok(())
17+
try!(rmdir_recursive(build_dir).chain_error(|| {
18+
human("Could not remove build directory")
19+
}))
2420
}
21+
22+
let doc_dir = manifest.get_doc_dir();
23+
if doc_dir.exists() {
24+
try!(rmdir_recursive(doc_dir).chain_error(|| {
25+
human("Could not remove documentation directory")
26+
}))
27+
}
28+
29+
Ok(())
2530
}

src/cargo/ops/cargo_compile.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ pub fn compile(manifest_path: &Path,
8585
debug!("packages={}", packages);
8686

8787
let targets = package.get_targets().iter().filter(|target| {
88-
target.get_profile().get_env() == env
88+
match env {
89+
// doc-all == document everything, so look for doc targets
90+
"doc" | "doc-all" => target.get_profile().get_env() == "doc",
91+
env => target.get_profile().get_env() == env,
92+
}
8993
}).collect::<Vec<&Target>>();
9094

9195
let mut config = try!(Config::new(*shell, update, jobs, target));

src/cargo/ops/cargo_doc.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::io::fs;
2+
3+
use ops;
4+
use util::CargoResult;
5+
use core::source::Source;
6+
use sources::PathSource;
7+
8+
pub struct DocOptions<'a> {
9+
pub all: bool,
10+
pub compile_opts: ops::CompileOptions<'a>,
11+
}
12+
13+
pub fn doc(manifest_path: &Path,
14+
options: &mut DocOptions) -> CargoResult<()> {
15+
let mut src = PathSource::for_path(&manifest_path.dir_path());
16+
try!(src.update());
17+
let root = try!(src.get_root_package());
18+
let output = root.get_manifest().get_target_dir().join("doc");
19+
let _ = fs::rmdir_recursive(&output);
20+
try!(ops::compile(manifest_path, &mut options.compile_opts));
21+
Ok(())
22+
}

src/cargo/ops/cargo_rustc/context.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,11 @@ impl<'a, 'b> Context<'a, 'b> {
216216

217217
pub fn is_relevant_target(&self, target: &Target) -> bool {
218218
target.is_lib() && match self.env {
219-
"test" => target.get_profile().is_compile(),
219+
"doc" | "test" => target.get_profile().is_compile(),
220+
// doc-all == document everything, so look for doc targets and
221+
// compile targets in dependencies
222+
"doc-all" => target.get_profile().is_compile() ||
223+
target.get_profile().is_doc(),
220224
_ => target.get_profile().get_env() == self.env,
221225
}
222226
}

src/cargo/ops/cargo_rustc/fingerprint.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub fn prepare(cx: &mut Context, pkg: &Package,
5656
}
5757

5858
for &target in targets.iter() {
59+
if target.get_profile().is_doc() { continue }
5960
let layout = cx.layout(target.get_profile().is_plugin());
6061
for filename in cx.target_filenames(target).iter() {
6162
let filename = filename.as_slice();

src/cargo/ops/cargo_rustc/layout.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,6 @@ impl<'a> LayoutProxy<'a> {
153153
pub fn old_native(&self, pkg: &Package) -> Path {
154154
self.root.old_native(pkg)
155155
}
156+
157+
pub fn proxy(&self) -> &'a Layout { self.root }
156158
}

src/cargo/ops/cargo_rustc/mod.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,12 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
110110
// interdependencies.
111111
let (mut libs, mut bins) = (Vec::new(), Vec::new());
112112
for &target in targets.iter() {
113-
let req = cx.get_requirement(pkg, target);
114-
let jobs = rustc(pkg, target, cx, req);
113+
let jobs = if target.get_profile().is_doc() {
114+
vec![rustdoc(pkg, target, cx)]
115+
} else {
116+
let req = cx.get_requirement(pkg, target);
117+
rustc(pkg, target, cx, req)
118+
};
115119
if target.is_lib() {
116120
libs.push_all_move(jobs);
117121
} else {
@@ -222,6 +226,44 @@ fn prepare_rustc(package: &Package, target: &Target, crate_types: Vec<&str>,
222226
}
223227
}
224228

229+
230+
fn rustdoc(package: &Package, target: &Target, cx: &mut Context) -> Job {
231+
// Can't document binaries, but they have a doc target listed so we can
232+
// build documentation of dependencies even when `cargo doc` is run.
233+
if target.is_bin() {
234+
return Job::new(proc() Ok(Vec::new()))
235+
}
236+
237+
let pkg_root = package.get_root();
238+
let cx_root = cx.layout(false).proxy().dest().dir_path().join("doc");
239+
let rustdoc = util::process("rustdoc").cwd(pkg_root.clone());
240+
let rustdoc = rustdoc.arg(target.get_src_path())
241+
.arg("-o").arg(cx_root)
242+
.arg("--crate-name").arg(target.get_name());
243+
let rustdoc = build_deps_args(rustdoc, target, package, cx, false);
244+
245+
log!(5, "commands={}", rustdoc);
246+
247+
let _ = cx.config.shell().verbose(|shell| {
248+
shell.status("Running", rustdoc.to_string())
249+
});
250+
251+
let primary = cx.primary;
252+
let name = package.get_name().to_string();
253+
Job::new(proc() {
254+
if primary {
255+
try!(rustdoc.exec().chain_error(|| {
256+
human(format!("Could not document `{}`.", name))
257+
}))
258+
} else {
259+
try!(rustdoc.exec_with_output().and(Ok(())).map_err(|err| {
260+
caused_human(format!("Could not document `{}`.\n{}",
261+
name, err.output().unwrap()), err)
262+
}))
263+
}
264+
Ok(Vec::new())
265+
})
266+
}
225267
fn build_base_args(mut cmd: ProcessBuilder,
226268
target: &Target,
227269
crate_types: &[&str]) -> ProcessBuilder {

src/cargo/ops/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
44
pub use self::cargo_rustc::compile_targets;
55
pub use self::cargo_run::run;
66
pub use self::cargo_new::{new, NewOptions};
7+
pub use self::cargo_doc::{doc, DocOptions};
78

89
mod cargo_clean;
910
mod cargo_compile;
1011
mod cargo_read_manifest;
1112
mod cargo_rustc;
1213
mod cargo_run;
1314
mod cargo_new;
15+
mod cargo_doc;

0 commit comments

Comments
 (0)