Skip to content

Commit eeb7af6

Browse files
committed
rustbuild: Build documentation for proc_macro
This commit fixes #38749 by building documentation for the `proc_macro` crate by default for configured hosts. Unfortunately did not turn out to be a trivial fix. Currently rustbuild generates documentation into multiple locations: one for std, one for test, and one for rustc. The initial fix for this issue simply actually executed `cargo doc -p proc_macro` which was otherwise completely elided before. Unfortunately rustbuild was the left to merge two documentation trees together. One for the standard library and one for the rustc tree (which only had docs for the `proc_macro` crate). Rustdoc itself knows how to merge documentation files (specifically around search indexes, etc) but rustbuild was unaware of this, so an initial fix ended up destroying the sidebar and the search bar from the libstd docs. To solve this issue the method of documentation has been tweaked slightly in rustbuild. The build system will not use symlinks (or directory junctions on Windows) to generate all documentation into the same location initially. This'll rely on rustdoc's logic to weave together all the output and ensure that it ends up all consistent. Closes #38749
1 parent 11bc48a commit eeb7af6

File tree

5 files changed

+202
-11
lines changed

5 files changed

+202
-11
lines changed

src/bootstrap/doc.rs

+54-9
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
2020
use std::fs::{self, File};
2121
use std::io::prelude::*;
22+
use std::io;
23+
use std::path::Path;
2224
use std::process::Command;
2325

2426
use {Build, Compiler, Mode};
25-
use util::cp_r;
27+
use util::{cp_r, symlink_dir};
2628
use build_helper::up_to_date;
2729

2830
/// Invoke `rustbook` as compiled in `stage` for `target` for the doc book
@@ -141,7 +143,22 @@ pub fn std(build: &Build, stage: u32, target: &str) {
141143
.join(target).join("doc");
142144
let rustdoc = build.rustdoc(&compiler);
143145

144-
build.clear_if_dirty(&out_dir, &rustdoc);
146+
// Here what we're doing is creating a *symlink* (directory junction on
147+
// Windows) to the final output location. This is not done as an
148+
// optimization but rather for correctness. We've got three trees of
149+
// documentation, one for std, one for test, and one for rustc. It's then
150+
// our job to merge them all together.
151+
//
152+
// Unfortunately rustbuild doesn't know nearly as well how to merge doc
153+
// trees as rustdoc does itself, so instead of actually having three
154+
// separate trees we just have rustdoc output to the same location across
155+
// all of them.
156+
//
157+
// This way rustdoc generates output directly into the output, and rustdoc
158+
// will also directly handle merging.
159+
let my_out = build.crate_doc_out(target);
160+
build.clear_if_dirty(&my_out, &rustdoc);
161+
t!(symlink_dir_force(&my_out, &out_dir));
145162

146163
let mut cargo = build.cargo(&compiler, Mode::Libstd, target, "doc");
147164
cargo.arg("--manifest-path")
@@ -166,7 +183,7 @@ pub fn std(build: &Build, stage: u32, target: &str) {
166183

167184

168185
build.run(&mut cargo);
169-
cp_r(&out_dir, &out)
186+
cp_r(&my_out, &out);
170187
}
171188

172189
/// Compile all libtest documentation.
@@ -187,13 +204,16 @@ pub fn test(build: &Build, stage: u32, target: &str) {
187204
.join(target).join("doc");
188205
let rustdoc = build.rustdoc(&compiler);
189206

190-
build.clear_if_dirty(&out_dir, &rustdoc);
207+
// See docs in std above for why we symlink
208+
let my_out = build.crate_doc_out(target);
209+
build.clear_if_dirty(&my_out, &rustdoc);
210+
t!(symlink_dir_force(&my_out, &out_dir));
191211

192212
let mut cargo = build.cargo(&compiler, Mode::Libtest, target, "doc");
193213
cargo.arg("--manifest-path")
194214
.arg(build.src.join("src/libtest/Cargo.toml"));
195215
build.run(&mut cargo);
196-
cp_r(&out_dir, &out)
216+
cp_r(&my_out, &out);
197217
}
198218

199219
/// Generate all compiler documentation.
@@ -213,15 +233,28 @@ pub fn rustc(build: &Build, stage: u32, target: &str) {
213233
let out_dir = build.stage_out(&compiler, Mode::Librustc)
214234
.join(target).join("doc");
215235
let rustdoc = build.rustdoc(&compiler);
216-
if !up_to_date(&rustdoc, &out_dir.join("rustc/index.html")) && out_dir.exists() {
217-
t!(fs::remove_dir_all(&out_dir));
218-
}
236+
237+
// See docs in std above for why we symlink
238+
let my_out = build.crate_doc_out(target);
239+
build.clear_if_dirty(&my_out, &rustdoc);
240+
t!(symlink_dir_force(&my_out, &out_dir));
241+
219242
let mut cargo = build.cargo(&compiler, Mode::Librustc, target, "doc");
220243
cargo.arg("--manifest-path")
221244
.arg(build.src.join("src/rustc/Cargo.toml"))
222245
.arg("--features").arg(build.rustc_features());
246+
247+
// Like with libstd above if compiler docs aren't enabled then we're not
248+
// documenting internal dependencies, so we have a whitelist.
249+
if !build.config.compiler_docs {
250+
cargo.arg("--no-deps");
251+
for krate in &["proc_macro"] {
252+
cargo.arg("-p").arg(krate);
253+
}
254+
}
255+
223256
build.run(&mut cargo);
224-
cp_r(&out_dir, &out)
257+
cp_r(&my_out, &out);
225258
}
226259

227260
/// Generates the HTML rendered error-index by running the
@@ -240,3 +273,15 @@ pub fn error_index(build: &Build, target: &str) {
240273

241274
build.run(&mut index);
242275
}
276+
277+
fn symlink_dir_force(src: &Path, dst: &Path) -> io::Result<()> {
278+
if let Ok(m) = fs::symlink_metadata(dst) {
279+
if m.file_type().is_dir() {
280+
try!(fs::remove_dir_all(dst));
281+
} else {
282+
try!(fs::remove_file(dst));
283+
}
284+
}
285+
286+
symlink_dir(src, dst)
287+
}

src/bootstrap/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,13 @@ impl Build {
707707
self.out.join(target).join("doc")
708708
}
709709

710+
/// Output directory for all crate documentation for a target (temporary)
711+
///
712+
/// The artifacts here are then copied into `doc_out` above.
713+
fn crate_doc_out(&self, target: &str) -> PathBuf {
714+
self.out.join(target).join("crate-docs")
715+
}
716+
710717
/// Returns true if no custom `llvm-config` is set for the specified target.
711718
///
712719
/// If no custom `llvm-config` was specified then Rust's llvm will be used.

src/bootstrap/step.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
640640
rules.doc(&krate.doc_step, path)
641641
.dep(|s| s.name("librustc-link"))
642642
.host(true)
643-
.default(default && build.config.compiler_docs)
643+
.default(default && build.config.docs)
644644
.run(move |s| doc::rustc(build, s.stage, s.target));
645645
}
646646

src/bootstrap/util.rs

+139
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use std::env;
1717
use std::ffi::OsString;
1818
use std::fs;
19+
use std::io;
1920
use std::path::{Path, PathBuf};
2021
use std::process::Command;
2122
use std::time::Instant;
@@ -175,3 +176,141 @@ impl Drop for TimeIt {
175176
time.subsec_nanos() / 1_000_000);
176177
}
177178
}
179+
180+
/// Symlinks two directories, using junctions on Windows and normal symlinks on
181+
/// Unix.
182+
pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
183+
let _ = fs::remove_dir(dest);
184+
return symlink_dir_inner(src, dest);
185+
186+
#[cfg(not(windows))]
187+
fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
188+
use std::os::unix::fs;
189+
fs::symlink(src, dest)
190+
}
191+
192+
// Creating a directory junction on windows involves dealing with reparse
193+
// points and the DeviceIoControl function, and this code is a skeleton of
194+
// what can be found here:
195+
//
196+
// http://www.flexhex.com/docs/articles/hard-links.phtml
197+
//
198+
// Copied from std
199+
#[cfg(windows)]
200+
#[allow(bad_style)]
201+
fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
202+
use std::ptr;
203+
use std::ffi::OsStr;
204+
use std::os::windows::ffi::OsStrExt;
205+
206+
const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
207+
const GENERIC_WRITE: DWORD = 0x40000000;
208+
const OPEN_EXISTING: DWORD = 3;
209+
const FILE_FLAG_OPEN_REPARSE_POINT: DWORD = 0x00200000;
210+
const FILE_FLAG_BACKUP_SEMANTICS: DWORD = 0x02000000;
211+
const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
212+
const IO_REPARSE_TAG_MOUNT_POINT: DWORD = 0xa0000003;
213+
const FILE_SHARE_DELETE: DWORD = 0x4;
214+
const FILE_SHARE_READ: DWORD = 0x1;
215+
const FILE_SHARE_WRITE: DWORD = 0x2;
216+
217+
type BOOL = i32;
218+
type DWORD = u32;
219+
type HANDLE = *mut u8;
220+
type LPCWSTR = *const u16;
221+
type LPDWORD = *mut DWORD;
222+
type LPOVERLAPPED = *mut u8;
223+
type LPSECURITY_ATTRIBUTES = *mut u8;
224+
type LPVOID = *mut u8;
225+
type WCHAR = u16;
226+
type WORD = u16;
227+
228+
#[repr(C)]
229+
struct REPARSE_MOUNTPOINT_DATA_BUFFER {
230+
ReparseTag: DWORD,
231+
ReparseDataLength: DWORD,
232+
Reserved: WORD,
233+
ReparseTargetLength: WORD,
234+
ReparseTargetMaximumLength: WORD,
235+
Reserved1: WORD,
236+
ReparseTarget: WCHAR,
237+
}
238+
239+
extern "system" {
240+
fn CreateFileW(lpFileName: LPCWSTR,
241+
dwDesiredAccess: DWORD,
242+
dwShareMode: DWORD,
243+
lpSecurityAttributes: LPSECURITY_ATTRIBUTES,
244+
dwCreationDisposition: DWORD,
245+
dwFlagsAndAttributes: DWORD,
246+
hTemplateFile: HANDLE)
247+
-> HANDLE;
248+
fn DeviceIoControl(hDevice: HANDLE,
249+
dwIoControlCode: DWORD,
250+
lpInBuffer: LPVOID,
251+
nInBufferSize: DWORD,
252+
lpOutBuffer: LPVOID,
253+
nOutBufferSize: DWORD,
254+
lpBytesReturned: LPDWORD,
255+
lpOverlapped: LPOVERLAPPED) -> BOOL;
256+
}
257+
258+
fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
259+
Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
260+
}
261+
262+
// We're using low-level APIs to create the junction, and these are more
263+
// picky about paths. For example, forward slashes cannot be used as a
264+
// path separator, so we should try to canonicalize the path first.
265+
let target = try!(fs::canonicalize(target));
266+
267+
try!(fs::create_dir(junction));
268+
269+
let path = try!(to_u16s(junction));
270+
271+
unsafe {
272+
let h = CreateFileW(path.as_ptr(),
273+
GENERIC_WRITE,
274+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
275+
0 as *mut _,
276+
OPEN_EXISTING,
277+
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
278+
ptr::null_mut());
279+
280+
let mut data = [0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
281+
let mut db = data.as_mut_ptr()
282+
as *mut REPARSE_MOUNTPOINT_DATA_BUFFER;
283+
let buf = &mut (*db).ReparseTarget as *mut _;
284+
let mut i = 0;
285+
// FIXME: this conversion is very hacky
286+
let v = br"\??\";
287+
let v = v.iter().map(|x| *x as u16);
288+
for c in v.chain(target.as_os_str().encode_wide().skip(4)) {
289+
*buf.offset(i) = c;
290+
i += 1;
291+
}
292+
*buf.offset(i) = 0;
293+
i += 1;
294+
(*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
295+
(*db).ReparseTargetMaximumLength = (i * 2) as WORD;
296+
(*db).ReparseTargetLength = ((i - 1) * 2) as WORD;
297+
(*db).ReparseDataLength =
298+
(*db).ReparseTargetLength as DWORD + 12;
299+
300+
let mut ret = 0;
301+
let res = DeviceIoControl(h as *mut _,
302+
FSCTL_SET_REPARSE_POINT,
303+
data.as_ptr() as *mut _,
304+
(*db).ReparseDataLength + 8,
305+
ptr::null_mut(), 0,
306+
&mut ret,
307+
ptr::null_mut());
308+
309+
if res == 0 {
310+
Err(io::Error::last_os_error())
311+
} else {
312+
Ok(())
313+
}
314+
}
315+
}
316+
}

src/libproc_macro/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//! This functionality is intended to be expanded over time as more surface
2222
//! area for macro authors is stabilized.
2323
//!
24-
//! See [the book](../../book/procedural-macros.html) for more.
24+
//! See [the book](../book/procedural-macros.html) for more.
2525
2626
#![crate_name = "proc_macro"]
2727
#![stable(feature = "proc_macro_lib", since = "1.15.0")]

0 commit comments

Comments
 (0)