cargo/compiler/
rustdoc.rs1use crate::compiler::build_runner::BuildRunner;
4use crate::compiler::unit::Unit;
5use crate::compiler::{BuildContext, CompileKind};
6use crate::context::{RustdocExternMap, RustdocExternMode};
7use crate::sources::CRATES_IO_REGISTRY;
8use crate::util::data_structures::HashMap;
9use crate::util::data_structures::HashSet;
10use crate::util::errors::{CargoResult, internal};
11use cargo_util::ProcessBuilder;
12use url::Url;
13
14fn build_all_urls(
19 build_runner: &BuildRunner<'_, '_>,
20 rustdoc: &mut ProcessBuilder,
21 unit: &Unit,
22 name2url: &HashMap<&String, Url>,
23 map: &RustdocExternMap,
24 unstable_opts: &mut bool,
25 seen: &mut HashSet<Unit>,
26) {
27 for dep in build_runner.unit_deps(unit) {
28 if !seen.insert(dep.unit.clone()) {
29 continue;
30 }
31 if !dep.unit.target.is_linkable() || dep.unit.mode.is_doc() {
32 continue;
33 }
34 for (registry, location) in &map.registries {
35 let sid = dep.unit.pkg.package_id().source_id();
36 let matches_registry = || -> bool {
37 if !sid.is_registry() {
38 return false;
39 }
40 if sid.is_crates_io() {
41 return registry == CRATES_IO_REGISTRY;
42 }
43 if let Some(index_url) = name2url.get(registry) {
44 return index_url == sid.url();
45 }
46 false
47 };
48 if matches_registry() {
49 let mut url = location.clone();
50 if !url.contains("{pkg_name}") && !url.contains("{version}") {
51 if !url.ends_with('/') {
52 url.push('/');
53 }
54 url.push_str("{pkg_name}/{version}/");
55 }
56 let url = url
57 .replace("{pkg_name}", &dep.unit.pkg.name())
58 .replace("{version}", &dep.unit.pkg.version().to_string());
59 rustdoc.arg("--extern-html-root-url");
60 rustdoc.arg(format!("{}={}", dep.unit.target.crate_name(), url));
61 *unstable_opts = true;
62 }
63 }
64 build_all_urls(
65 build_runner,
66 rustdoc,
67 &dep.unit,
68 name2url,
69 map,
70 unstable_opts,
71 seen,
72 );
73 }
74}
75
76pub fn add_root_urls(
82 build_runner: &BuildRunner<'_, '_>,
83 unit: &Unit,
84 rustdoc: &mut ProcessBuilder,
85) -> CargoResult<()> {
86 let gctx = build_runner.bcx.gctx;
87 if !gctx.cli_unstable().rustdoc_map {
88 tracing::debug!("`doc.extern-map` ignored, requires -Zrustdoc-map flag");
89 return Ok(());
90 }
91 let map = gctx.doc_extern_map()?;
92 let mut unstable_opts = false;
93 let name2url: HashMap<&String, Url> = map
95 .registries
96 .keys()
97 .filter_map(|name| {
98 if let Ok(index_url) = gctx.get_registry_index(name) {
99 Some((name, index_url))
100 } else {
101 tracing::warn!(
102 "`doc.extern-map.{}` specifies a registry that is not defined",
103 name
104 );
105 None
106 }
107 })
108 .collect();
109 build_all_urls(
110 build_runner,
111 rustdoc,
112 unit,
113 &name2url,
114 map,
115 &mut unstable_opts,
116 &mut HashSet::default(),
117 );
118 let std_url = match &map.std {
119 None | Some(RustdocExternMode::Remote) => None,
120 Some(RustdocExternMode::Local) => {
121 let sysroot = &build_runner.bcx.target_data.info(CompileKind::Host).sysroot;
122 let html_root = sysroot.join("share").join("doc").join("rust").join("html");
123 if html_root.exists() {
124 let url = Url::from_file_path(&html_root).map_err(|()| {
125 internal(format!(
126 "`{}` failed to convert to URL",
127 html_root.display()
128 ))
129 })?;
130 Some(url.to_string())
131 } else {
132 tracing::warn!(
133 "`doc.extern-map.std` is \"local\", but local docs don't appear to exist at {}",
134 html_root.display()
135 );
136 None
137 }
138 }
139 Some(RustdocExternMode::Url(s)) => Some(s.to_string()),
140 };
141 if let Some(url) = std_url {
142 for name in &["std", "core", "alloc", "proc_macro"] {
143 rustdoc.arg("--extern-html-root-url");
144 rustdoc.arg(format!("{}={}", name, url));
145 unstable_opts = true;
146 }
147 }
148
149 if unstable_opts {
150 rustdoc.arg("-Zunstable-options");
151 }
152 Ok(())
153}
154
155pub fn add_output_format(
160 build_runner: &BuildRunner<'_, '_>,
161 rustdoc: &mut ProcessBuilder,
162) -> CargoResult<()> {
163 if build_runner.bcx.build_config.intent.wants_doc_json_output() {
164 rustdoc.arg("-Zunstable-options");
165 rustdoc.arg("--output-format=json");
166 }
167
168 Ok(())
169}
170
171#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy)]
177pub enum RustdocScrapeExamples {
178 Enabled,
179 Disabled,
180 Unset,
181}
182
183impl RustdocScrapeExamples {
184 pub fn is_enabled(&self) -> bool {
185 matches!(self, RustdocScrapeExamples::Enabled)
186 }
187
188 pub fn is_unset(&self) -> bool {
189 matches!(self, RustdocScrapeExamples::Unset)
190 }
191}
192
193impl BuildContext<'_, '_> {
194 pub fn scrape_units_have_dep_on<'a>(&'a self, unit: &'a Unit) -> Vec<&'a Unit> {
204 self.scrape_units
205 .iter()
206 .filter(|scrape_unit| {
207 self.unit_graph[scrape_unit]
208 .iter()
209 .any(|dep| &dep.unit == unit && !dep.unit.mode.is_run_custom_build())
210 })
211 .collect()
212 }
213
214 pub fn unit_can_fail_for_docscraping(&self, unit: &Unit) -> bool {
217 let for_scrape_units = if unit.mode.is_doc_scrape() {
221 vec![unit]
222 } else {
223 self.scrape_units_have_dep_on(unit)
224 };
225
226 if for_scrape_units.is_empty() {
227 false
228 } else {
229 for_scrape_units
232 .iter()
233 .all(|unit| unit.target.doc_scrape_examples().is_unset())
234 }
235 }
236}