Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rustdoc root URL mappings. #8287

Merged
merged 5 commits into from
May 30, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add support for rustdoc root URL mappings.
  • Loading branch information
ehuss committed May 26, 2020
commit e0f9643b0f238d7d2bb7682bad37a0b82d17b704
22 changes: 21 additions & 1 deletion src/cargo/core/compiler/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
//! mtime of sources | ✓[^3] |
//! RUSTFLAGS/RUSTDOCFLAGS | ✓ |
//! LTO flags | ✓ |
//! config settings[^5] | ✓ |
//! is_std | | ✓
//!
//! [^1]: Build script and bin dependencies are not included.
Expand All @@ -82,6 +83,9 @@
//! [^4]: `__CARGO_DEFAULT_LIB_METADATA` is set by rustbuild to embed the
//! release channel (bootstrap/stable/beta/nightly) in libstd.
//!
//! [^5]: Config settings that are not otherwise captured anywhere else.
//! Currently, this is only `doc.extern-map`.
//!
//! When deciding what should go in the Metadata vs the Fingerprint, consider
//! that some files (like dylibs) do not have a hash in their filename. Thus,
//! if a value changes, only the fingerprint will detect the change (consider,
Expand Down Expand Up @@ -533,6 +537,8 @@ pub struct Fingerprint {
/// "description", which are exposed as environment variables during
/// compilation.
metadata: u64,
/// Hash of various config settings that change how things are compiled.
config: u64,
/// Description of whether the filesystem status for this unit is up to date
/// or should be considered stale.
#[serde(skip)]
Expand Down Expand Up @@ -746,6 +752,7 @@ impl Fingerprint {
memoized_hash: Mutex::new(None),
rustflags: Vec::new(),
metadata: 0,
config: 0,
fs_status: FsStatus::Stale,
outputs: Vec::new(),
}
Expand Down Expand Up @@ -806,6 +813,9 @@ impl Fingerprint {
if self.metadata != old.metadata {
bail!("metadata changed")
}
if self.config != old.config {
bail!("configuration settings have changed")
}
let my_local = self.local.lock().unwrap();
let old_local = old.local.lock().unwrap();
if my_local.len() != old_local.len() {
Expand Down Expand Up @@ -1040,12 +1050,13 @@ impl hash::Hash for Fingerprint {
ref deps,
ref local,
metadata,
config,
ref rustflags,
..
} = *self;
let local = local.lock().unwrap();
(
rustc, features, target, path, profile, &*local, metadata, rustflags,
rustc, features, target, path, profile, &*local, metadata, config, rustflags,
)
.hash(h);

Expand Down Expand Up @@ -1252,6 +1263,14 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
// Include metadata since it is exposed as environment variables.
let m = unit.pkg.manifest().metadata();
let metadata = util::hash_u64((&m.authors, &m.description, &m.homepage, &m.repository));
let config = if unit.mode.is_doc() && cx.bcx.config.cli_unstable().rustdoc_map {
cx.bcx
.config
.doc_extern_map()
.map_or(0, |map| util::hash_u64(map))
} else {
0
};
Ok(Fingerprint {
rustc: util::hash_u64(&cx.bcx.rustc().verbose_version),
target: util::hash_u64(&unit.target),
Expand All @@ -1264,6 +1283,7 @@ fn calculate_normal(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Finger
local: Mutex::new(local),
memoized_hash: Mutex::new(None),
metadata,
config,
rustflags: extra_flags,
fs_status: FsStatus::Stale,
outputs,
Expand Down
2 changes: 2 additions & 0 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod layout;
mod links;
mod lto;
mod output_depinfo;
pub mod rustdoc;
pub mod standard_lib;
mod timings;
mod unit;
Expand Down Expand Up @@ -570,6 +571,7 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Work> {
}

build_deps_args(&mut rustdoc, cx, unit)?;
rustdoc::add_root_urls(cx, unit, &mut rustdoc)?;

rustdoc.args(bcx.rustdocflags_args(unit));

Expand Down
172 changes: 172 additions & 0 deletions src/cargo/core/compiler/rustdoc.rs
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(())
}
2 changes: 2 additions & 0 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ pub struct CliUnstable {
pub crate_versions: bool,
pub separate_nightlies: bool,
pub multitarget: bool,
pub rustdoc_map: bool,
}

impl CliUnstable {
Expand Down Expand Up @@ -435,6 +436,7 @@ impl CliUnstable {
"crate-versions" => self.crate_versions = parse_empty(k, v)?,
"separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
"multitarget" => self.multitarget = parse_empty(k, v)?,
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
_ => bail!("unknown `-Z` flag specified: {}", k),
}

Expand Down
11 changes: 11 additions & 0 deletions src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ use serde::Deserialize;
use url::Url;

use self::ConfigValue as CV;
use crate::core::compiler::rustdoc::RustdocExternMap;
use crate::core::shell::Verbosity;
use crate::core::{nightly_features_allowed, CliUnstable, Shell, SourceId, Workspace};
use crate::ops;
Expand Down Expand Up @@ -172,6 +173,7 @@ pub struct Config {
net_config: LazyCell<CargoNetConfig>,
build_config: LazyCell<CargoBuildConfig>,
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
doc_extern_map: LazyCell<RustdocExternMap>,
}

impl Config {
Expand Down Expand Up @@ -241,6 +243,7 @@ impl Config {
net_config: LazyCell::new(),
build_config: LazyCell::new(),
target_cfgs: LazyCell::new(),
doc_extern_map: LazyCell::new(),
}
}

Expand Down Expand Up @@ -1159,6 +1162,14 @@ impl Config {
.try_borrow_with(|| target::load_target_cfgs(self))
}

pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
// Note: This does not support environment variables. The `Unit`
// fundamentally does not have access to the registry name, so there is
// nothing to query. Plumbing the name into SourceId is quite challenging.
self.doc_extern_map
.try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
}

/// Returns the `[target]` table definition for the given target triple.
pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
target::load_target_triple(self, target)
Expand Down
41 changes: 41 additions & 0 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
Expand Up @@ -785,3 +785,44 @@ strip = "debuginfo"

Other possible values of `strip` are `none` and `symbols`. The default is
`none`.

### rustdoc-map
* Tracking Issue: TODO

This feature adds configuration settings that are passed to `rustdoc` so that
it can generate links to dependencies whose documentation is hosted elsewhere
when the dependency is not documented. First, add this to `.cargo/config`:

```toml
[doc.extern-map.registries]
crates-io = "https://docs.rs/"
```

Then, when building documentation, use the following flags to cause links
to dependencies to link to [docs.rs](https://docs.rs/):

```
cargo +nightly doc --no-deps -Zrustdoc-map
```

The `registries` table contains a mapping of registry name to the URL to link
to. The URL may have the markers `{pkg_name}` and `{version}` which will get
replaced with the corresponding values. If neither are specified, then Cargo
defaults to appending `{pkg_name}/{version}/` to the end of the URL.

Another config setting is available to redirect standard library links. By
default, rustdoc creates links to <https://doc.rust-lang.org/nightly/>. To
change this behavior, use the `doc.extern-map.std` setting:

```toml
[doc.extern-map]
std = "local"
```

A value of `"local"` means to link to the documentation found in the `rustc`
sysroot. If you are using rustup, this documentation can be installed with
`rustup component add rust-docs`.

The default value is `"remote"`.

The value may also take a URL for a custom location.
1 change: 1 addition & 0 deletions tests/testsuite/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ mod run;
mod rustc;
mod rustc_info_cache;
mod rustdoc;
mod rustdoc_extern_html;
mod rustdocflags;
mod rustflags;
mod search;
Expand Down
Loading