-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #8287 - ehuss:rustdoc-map, r=alexcrichton
Add support for rustdoc root URL mappings. This adds an experimental configuration setting to allow Cargo to pass the `--extern-html-root-url` flag to rustdoc. This flag allows rustdoc to link to other locations when a dependency is not locally documented. See the documentation in `unstable.md` for more details. There are some known issues with this implementation: * Rustdoc doesn't seem to know much about renamed dependencies. The links it generates are to the package name, not the renamed name. The code is written to pass in package names, but if there are multiple dependencies to the same package, it won't work properly. * Similarly, if there are multiple versions of the same package within the dep graph, rustdoc will only link to one of them. To fix this, Cargo would need to pass metadata info into rustdoc (such as the package version). * If a dependency is built with different features than what is on docs.rs, some links may break. * This explodes the command-line length significantly. Before stabilizing, we may want to consider addressing that. I'm not sure if it would make sense to change rustdoc's interface, or to use response files? * This does not pass mappings for transitive dependencies. This normally isn't an issue, but can arise for re-exports (see the `alt_registry` test for an example). I'm not sure if this is a bug in rustdoc or not (there is a large number of issues regarding reexports and rustdoc). Cargo could include these, but this would make the command-line length even longer. Not sure what to do here. * The config value does not support environment variables. This would be very difficult to support, because Cargo doesn't retain the registry name in `SourceId`. I looked into fixing that, but it is very difficult, and hard to make it reliable. I have tried to consider future changes in this design, to ensure it doesn't make them more difficult: * Single-tab browsing. This would be a mode where the std docs are merged with the local crate's docs so that the std docs are shown in the same place (and included in the index). This could be expressed with something like `doc.extern-map.std = "include"` or something like that. (Or maybe just use build-std?) * Direct-dependencies only. Often transitive dependencies aren't that interesting, and take up a lot of space in the output, and clog the search index. Some users want the ability to (locally) document their package + direct dependencies only. I think this could be implemented with some kind of command-line flag, perhaps with a config setting in the `[doc]` table. `--extern-html-root-url` flag will automatically handle second-level dependencies. * Manual-exclusions. Sometimes there are specific dependencies that are very expensive to document locally, but you still want everything else. I think this could be implemented with a command-line flag (`--exclude winapi`?), and the rustdoc-map feature would automatically link those excluded crates' items to docs.rs. This could also be added to the `[doc]` table. We can also consider at any time to change the defaults (such as making `crates-io = "https://docs.rs"` the default). It could also potentially auto-detect `std = "local"`, although rustdoc could do the same internally. Closes #6279
- Loading branch information
Showing
10 changed files
with
650 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
//! Utilities for building with rustdoc. | ||
use crate::core::compiler::context::Context; | ||
use crate::core::compiler::unit::Unit; | ||
use crate::core::compiler::CompileKind; | ||
use crate::sources::CRATES_IO_REGISTRY; | ||
use crate::util::errors::{internal, CargoResult}; | ||
use crate::util::ProcessBuilder; | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::hash; | ||
use url::Url; | ||
|
||
/// Mode used for `std`. | ||
#[derive(Debug, Hash)] | ||
pub enum RustdocExternMode { | ||
/// Use a local `file://` URL. | ||
Local, | ||
/// Use a remote URL to https://doc.rust-lang.org/ (default). | ||
Remote, | ||
/// An arbitrary URL. | ||
Url(String), | ||
} | ||
|
||
impl From<String> for RustdocExternMode { | ||
fn from(s: String) -> RustdocExternMode { | ||
match s.as_ref() { | ||
"local" => RustdocExternMode::Local, | ||
"remote" => RustdocExternMode::Remote, | ||
_ => RustdocExternMode::Url(s), | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for RustdocExternMode { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
match self { | ||
RustdocExternMode::Local => "local".fmt(f), | ||
RustdocExternMode::Remote => "remote".fmt(f), | ||
RustdocExternMode::Url(s) => s.fmt(f), | ||
} | ||
} | ||
} | ||
|
||
impl<'de> serde::de::Deserialize<'de> for RustdocExternMode { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: serde::de::Deserializer<'de>, | ||
{ | ||
let s = String::deserialize(deserializer)?; | ||
Ok(s.into()) | ||
} | ||
} | ||
|
||
#[derive(serde::Deserialize, Debug)] | ||
pub struct RustdocExternMap { | ||
registries: HashMap<String, String>, | ||
std: Option<RustdocExternMode>, | ||
} | ||
|
||
impl hash::Hash for RustdocExternMap { | ||
fn hash<H: hash::Hasher>(&self, into: &mut H) { | ||
self.std.hash(into); | ||
for (key, value) in &self.registries { | ||
key.hash(into); | ||
value.hash(into); | ||
} | ||
} | ||
} | ||
|
||
pub fn add_root_urls( | ||
cx: &Context<'_, '_>, | ||
unit: &Unit, | ||
rustdoc: &mut ProcessBuilder, | ||
) -> CargoResult<()> { | ||
let config = cx.bcx.config; | ||
if !config.cli_unstable().rustdoc_map { | ||
log::debug!("`doc.extern-map` ignored, requires -Zrustdoc-map flag"); | ||
return Ok(()); | ||
} | ||
let map = config.doc_extern_map()?; | ||
if map.registries.len() == 0 && map.std.is_none() { | ||
// Skip doing unnecessary work. | ||
return Ok(()); | ||
} | ||
let mut unstable_opts = false; | ||
// Collect mapping of registry name -> index url. | ||
let name2url: HashMap<&String, Url> = map | ||
.registries | ||
.keys() | ||
.filter_map(|name| { | ||
if let Ok(index_url) = config.get_registry_index(name) { | ||
return Some((name, index_url)); | ||
} else { | ||
log::warn!( | ||
"`doc.extern-map.{}` specifies a registry that is not defined", | ||
name | ||
); | ||
return None; | ||
} | ||
}) | ||
.collect(); | ||
for dep in cx.unit_deps(unit) { | ||
if dep.unit.target.is_linkable() && !dep.unit.mode.is_doc() { | ||
for (registry, location) in &map.registries { | ||
let sid = dep.unit.pkg.package_id().source_id(); | ||
let matches_registry = || -> bool { | ||
if !sid.is_registry() { | ||
return false; | ||
} | ||
if sid.is_default_registry() { | ||
return registry == CRATES_IO_REGISTRY; | ||
} | ||
if let Some(index_url) = name2url.get(registry) { | ||
return index_url == sid.url(); | ||
} | ||
false | ||
}; | ||
if matches_registry() { | ||
let mut url = location.clone(); | ||
if !url.contains("{pkg_name}") && !url.contains("{version}") { | ||
if !url.ends_with('/') { | ||
url.push('/'); | ||
} | ||
url.push_str("{pkg_name}/{version}/"); | ||
} | ||
let url = url | ||
.replace("{pkg_name}", &dep.unit.pkg.name()) | ||
.replace("{version}", &dep.unit.pkg.version().to_string()); | ||
rustdoc.arg("--extern-html-root-url"); | ||
rustdoc.arg(format!("{}={}", dep.unit.target.crate_name(), url)); | ||
unstable_opts = true; | ||
} | ||
} | ||
} | ||
} | ||
let std_url = match &map.std { | ||
None | Some(RustdocExternMode::Remote) => None, | ||
Some(RustdocExternMode::Local) => { | ||
let sysroot = &cx.bcx.target_data.info(CompileKind::Host).sysroot; | ||
let html_root = sysroot.join("share").join("doc").join("rust").join("html"); | ||
if html_root.exists() { | ||
let url = Url::from_file_path(&html_root).map_err(|()| { | ||
internal(format!( | ||
"`{}` failed to convert to URL", | ||
html_root.display() | ||
)) | ||
})?; | ||
Some(url.to_string()) | ||
} else { | ||
log::warn!( | ||
"`doc.extern-map.std` is \"local\", but local docs don't appear to exist at {}", | ||
html_root.display() | ||
); | ||
None | ||
} | ||
} | ||
Some(RustdocExternMode::Url(s)) => Some(s.to_string()), | ||
}; | ||
if let Some(url) = std_url { | ||
for name in &["std", "core", "alloc", "proc_macro"] { | ||
rustdoc.arg("--extern-html-root-url"); | ||
rustdoc.arg(format!("{}={}", name, url)); | ||
unstable_opts = true; | ||
} | ||
} | ||
|
||
if unstable_opts { | ||
rustdoc.arg("-Zunstable-options"); | ||
} | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.