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