1 //! Utilities for building with rustdoc.
2
3 use crate::core::compiler::context::Context;
4 use crate::core::compiler::unit::Unit;
5 use crate::core::compiler::CompileKind;
6 use crate::sources::CRATES_IO_REGISTRY;
7 use crate::util::errors::{internal, CargoResult};
8 use cargo_util::ProcessBuilder;
9 use std::collections::HashMap;
10 use std::fmt;
11 use std::hash;
12 use url::Url;
13
14 /// Mode used for `std`.
15 #[derive(Debug, Hash)]
16 pub enum RustdocExternMode {
17 /// Use a local `file://` URL.
18 Local,
19 /// Use a remote URL to <https://doc.rust-lang.org/> (default).
20 Remote,
21 /// An arbitrary URL.
22 Url(String),
23 }
24
25 impl From<String> for RustdocExternMode {
from(s: String) -> RustdocExternMode26 fn from(s: String) -> RustdocExternMode {
27 match s.as_ref() {
28 "local" => RustdocExternMode::Local,
29 "remote" => RustdocExternMode::Remote,
30 _ => RustdocExternMode::Url(s),
31 }
32 }
33 }
34
35 impl fmt::Display for RustdocExternMode {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 RustdocExternMode::Local => "local".fmt(f),
39 RustdocExternMode::Remote => "remote".fmt(f),
40 RustdocExternMode::Url(s) => s.fmt(f),
41 }
42 }
43 }
44
45 impl<'de> serde::de::Deserialize<'de> for RustdocExternMode {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::de::Deserializer<'de>,46 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
47 where
48 D: serde::de::Deserializer<'de>,
49 {
50 let s = String::deserialize(deserializer)?;
51 Ok(s.into())
52 }
53 }
54
55 #[derive(serde::Deserialize, Debug)]
56 #[serde(default)]
57 pub struct RustdocExternMap {
58 #[serde(deserialize_with = "default_crates_io_to_docs_rs")]
59 pub(crate) registries: HashMap<String, String>,
60 std: Option<RustdocExternMode>,
61 }
62
63 impl Default for RustdocExternMap {
default() -> Self64 fn default() -> Self {
65 let mut registries = HashMap::new();
66 registries.insert("crates-io".into(), "https://docs.rs/".into());
67 Self {
68 registries,
69 std: None,
70 }
71 }
72 }
73
default_crates_io_to_docs_rs<'de, D: serde::Deserializer<'de>>( de: D, ) -> Result<HashMap<String, String>, D::Error>74 fn default_crates_io_to_docs_rs<'de, D: serde::Deserializer<'de>>(
75 de: D,
76 ) -> Result<HashMap<String, String>, D::Error> {
77 use serde::Deserialize;
78 let mut registries = HashMap::deserialize(de)?;
79 if !registries.contains_key("crates-io") {
80 registries.insert("crates-io".into(), "https://docs.rs/".into());
81 }
82 Ok(registries)
83 }
84
85 impl hash::Hash for RustdocExternMap {
hash<H: hash::Hasher>(&self, into: &mut H)86 fn hash<H: hash::Hasher>(&self, into: &mut H) {
87 self.std.hash(into);
88 for (key, value) in &self.registries {
89 key.hash(into);
90 value.hash(into);
91 }
92 }
93 }
94
add_root_urls( cx: &Context<'_, '_>, unit: &Unit, rustdoc: &mut ProcessBuilder, ) -> CargoResult<()>95 pub fn add_root_urls(
96 cx: &Context<'_, '_>,
97 unit: &Unit,
98 rustdoc: &mut ProcessBuilder,
99 ) -> CargoResult<()> {
100 let config = cx.bcx.config;
101 if !config.cli_unstable().rustdoc_map {
102 log::debug!("`doc.extern-map` ignored, requires -Zrustdoc-map flag");
103 return Ok(());
104 }
105 let map = config.doc_extern_map()?;
106 let mut unstable_opts = false;
107 // Collect mapping of registry name -> index url.
108 let name2url: HashMap<&String, Url> = map
109 .registries
110 .keys()
111 .filter_map(|name| {
112 if let Ok(index_url) = config.get_registry_index(name) {
113 Some((name, index_url))
114 } else {
115 log::warn!(
116 "`doc.extern-map.{}` specifies a registry that is not defined",
117 name
118 );
119 None
120 }
121 })
122 .collect();
123 for dep in cx.unit_deps(unit) {
124 if dep.unit.target.is_linkable() && !dep.unit.mode.is_doc() {
125 for (registry, location) in &map.registries {
126 let sid = dep.unit.pkg.package_id().source_id();
127 let matches_registry = || -> bool {
128 if !sid.is_registry() {
129 return false;
130 }
131 if sid.is_default_registry() {
132 return registry == CRATES_IO_REGISTRY;
133 }
134 if let Some(index_url) = name2url.get(registry) {
135 return index_url == sid.url();
136 }
137 false
138 };
139 if matches_registry() {
140 let mut url = location.clone();
141 if !url.contains("{pkg_name}") && !url.contains("{version}") {
142 if !url.ends_with('/') {
143 url.push('/');
144 }
145 url.push_str("{pkg_name}/{version}/");
146 }
147 let url = url
148 .replace("{pkg_name}", &dep.unit.pkg.name())
149 .replace("{version}", &dep.unit.pkg.version().to_string());
150 rustdoc.arg("--extern-html-root-url");
151 rustdoc.arg(format!("{}={}", dep.unit.target.crate_name(), url));
152 unstable_opts = true;
153 }
154 }
155 }
156 }
157 let std_url = match &map.std {
158 None | Some(RustdocExternMode::Remote) => None,
159 Some(RustdocExternMode::Local) => {
160 let sysroot = &cx.bcx.target_data.info(CompileKind::Host).sysroot;
161 let html_root = sysroot.join("share").join("doc").join("rust").join("html");
162 if html_root.exists() {
163 let url = Url::from_file_path(&html_root).map_err(|()| {
164 internal(format!(
165 "`{}` failed to convert to URL",
166 html_root.display()
167 ))
168 })?;
169 Some(url.to_string())
170 } else {
171 log::warn!(
172 "`doc.extern-map.std` is \"local\", but local docs don't appear to exist at {}",
173 html_root.display()
174 );
175 None
176 }
177 }
178 Some(RustdocExternMode::Url(s)) => Some(s.to_string()),
179 };
180 if let Some(url) = std_url {
181 for name in &["std", "core", "alloc", "proc_macro"] {
182 rustdoc.arg("--extern-html-root-url");
183 rustdoc.arg(format!("{}={}", name, url));
184 unstable_opts = true;
185 }
186 }
187
188 if unstable_opts {
189 rustdoc.arg("-Zunstable-options");
190 }
191 Ok(())
192 }
193