]> git.proxmox.com Git - cargo.git/blame - src/cargo/core/compiler/future_incompat.rs
Remove the update method on registry functions. Instead of explicitly
[cargo.git] / src / cargo / core / compiler / future_incompat.rs
CommitLineData
c2b02b39
EH
1//! Support for future-incompatible warning reporting.
2
f57be6f6
AH
3use crate::core::compiler::BuildContext;
4use crate::core::{Dependency, PackageId, Workspace};
5use crate::sources::SourceConfigMap;
c2b02b39
EH
6use crate::util::{iter_join, CargoResult, Config};
7use anyhow::{bail, format_err, Context};
6177c658 8use serde::{Deserialize, Serialize};
f57be6f6
AH
9use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
10use std::fmt::Write as _;
c2b02b39 11use std::io::{Read, Write};
82093ad9 12use std::task::Poll;
6177c658 13
71cb59b6
EH
14pub const REPORT_PREAMBLE: &str = "\
15The following warnings were discovered during the build. These warnings are an
16indication that the packages contain code that will become an error in a
17future release of Rust. These warnings typically cover changes to close
18soundness problems, unintended or undocumented behavior, or critical problems
19that cannot be fixed in a backwards-compatible fashion, and are not expected
20to be in wide use.
21
22Each warning should contain a link for more information on what the warning
23means and how to resolve it.
24";
25
7e2b31a6
EH
26/// Current version of the on-disk format.
27const ON_DISK_VERSION: u32 = 0;
28
6177c658
AH
29/// The future incompatibility report, emitted by the compiler as a JSON message.
30#[derive(serde::Deserialize)]
31pub struct FutureIncompatReport {
32 pub future_incompat_report: Vec<FutureBreakageItem>,
33}
34
c2b02b39
EH
35/// Structure used for collecting reports in-memory.
36pub struct FutureIncompatReportPackage {
37 pub package_id: PackageId,
38 pub items: Vec<FutureBreakageItem>,
39}
40
41/// A single future-incompatible warning emitted by rustc.
6177c658
AH
42#[derive(Serialize, Deserialize)]
43pub struct FutureBreakageItem {
44 /// The date at which this lint will become an error.
45 /// Currently unused
46 pub future_breakage_date: Option<String>,
47 /// The original diagnostic emitted by the compiler
48 pub diagnostic: Diagnostic,
49}
50
f03d47ce 51/// A diagnostic emitted by the compiler as a JSON message.
6177c658
AH
52/// We only care about the 'rendered' field
53#[derive(Serialize, Deserialize)]
54pub struct Diagnostic {
55 pub rendered: String,
9c7cc545 56 pub level: String,
6177c658
AH
57}
58
59/// The filename in the top-level `target` directory where we store
60/// the report
c2b02b39
EH
61const FUTURE_INCOMPAT_FILE: &str = ".future-incompat-report.json";
62/// Max number of reports to save on disk.
63const MAX_REPORTS: usize = 5;
64
65/// The structure saved to disk containing the reports.
66#[derive(Serialize, Deserialize)]
67pub struct OnDiskReports {
68 /// A schema version number, to handle older cargo's from trying to read
69 /// something that they don't understand.
70 version: u32,
71 /// The report ID to use for the next report to save.
72 next_id: u32,
73 /// Available reports.
74 reports: Vec<OnDiskReport>,
75}
6177c658 76
c2b02b39 77/// A single report for a given compilation session.
6177c658 78#[derive(Serialize, Deserialize)]
c2b02b39
EH
79struct OnDiskReport {
80 /// Unique reference to the report for the `--id` CLI flag.
81 id: u32,
958f2fc9
AH
82 /// A message describing suggestions for fixing the
83 /// reported issues
84 suggestion_message: String,
c2b02b39 85 /// Report, suitable for printing to the console.
315d605b 86 /// Maps package names to the corresponding report
d5538c38
AH
87 /// We use a `BTreeMap` so that the iteration order
88 /// is stable across multiple runs of `cargo`
315d605b 89 per_package: BTreeMap<String, String>,
c2b02b39
EH
90}
91
92impl Default for OnDiskReports {
93 fn default() -> OnDiskReports {
94 OnDiskReports {
7e2b31a6 95 version: ON_DISK_VERSION,
c2b02b39
EH
96 next_id: 1,
97 reports: Vec::new(),
98 }
99 }
100}
101
102impl OnDiskReports {
103 /// Saves a new report.
104 pub fn save_report(
958f2fc9 105 mut self,
c2b02b39 106 ws: &Workspace<'_>,
958f2fc9 107 suggestion_message: String,
c2b02b39 108 per_package_reports: &[FutureIncompatReportPackage],
958f2fc9 109 ) {
c2b02b39 110 let report = OnDiskReport {
958f2fc9
AH
111 id: self.next_id,
112 suggestion_message,
315d605b 113 per_package: render_report(per_package_reports),
c2b02b39 114 };
958f2fc9
AH
115 self.next_id += 1;
116 self.reports.push(report);
117 if self.reports.len() > MAX_REPORTS {
118 self.reports.remove(0);
c2b02b39 119 }
958f2fc9 120 let on_disk = serde_json::to_vec(&self).unwrap();
c2b02b39
EH
121 if let Err(e) = ws
122 .target_dir()
123 .open_rw(
124 FUTURE_INCOMPAT_FILE,
125 ws.config(),
126 "Future incompatibility report",
127 )
128 .and_then(|file| {
129 let mut file = file.file();
130 file.set_len(0)?;
131 file.write_all(&on_disk)?;
132 Ok(())
133 })
134 {
135 crate::display_warning_with_error(
136 "failed to write on-disk future incompatible report",
137 &e,
138 &mut ws.config().shell(),
139 );
140 }
c2b02b39
EH
141 }
142
143 /// Loads the on-disk reports.
144 pub fn load(ws: &Workspace<'_>) -> CargoResult<OnDiskReports> {
145 let report_file = match ws.target_dir().open_ro(
146 FUTURE_INCOMPAT_FILE,
147 ws.config(),
148 "Future incompatible report",
149 ) {
150 Ok(r) => r,
151 Err(e) => {
152 if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
153 if io_err.kind() == std::io::ErrorKind::NotFound {
154 bail!("no reports are currently available");
155 }
156 }
157 return Err(e);
158 }
159 };
160
161 let mut file_contents = String::new();
162 report_file
163 .file()
164 .read_to_string(&mut file_contents)
165 .with_context(|| "failed to read report")?;
166 let on_disk_reports: OnDiskReports =
167 serde_json::from_str(&file_contents).with_context(|| "failed to load report")?;
7e2b31a6 168 if on_disk_reports.version != ON_DISK_VERSION {
c2b02b39
EH
169 bail!("unable to read reports; reports were saved from a future version of Cargo");
170 }
171 Ok(on_disk_reports)
172 }
173
174 /// Returns the most recent report ID.
175 pub fn last_id(&self) -> u32 {
176 self.reports.last().map(|r| r.id).unwrap()
177 }
178
d5538c38
AH
179 pub fn get_report(
180 &self,
181 id: u32,
182 config: &Config,
183 package: Option<&str>,
184 ) -> CargoResult<String> {
c2b02b39
EH
185 let report = self.reports.iter().find(|r| r.id == id).ok_or_else(|| {
186 let available = iter_join(self.reports.iter().map(|r| r.id.to_string()), ", ");
187 format_err!(
188 "could not find report with ID {}\n\
189 Available IDs are: {}",
190 id,
191 available
192 )
193 })?;
6f185070 194
958f2fc9
AH
195 let mut to_display = report.suggestion_message.clone();
196 to_display += "\n";
6f185070
AH
197
198 let package_report = if let Some(package) = package {
d5538c38 199 report
315d605b 200 .per_package
d5538c38
AH
201 .get(package)
202 .ok_or_else(|| {
203 format_err!(
204 "could not find package with ID `{}`\n
205 Available packages are: {}\n
f57be6f6 206 Omit the `--package` flag to display a report for all packages",
d5538c38 207 package,
315d605b 208 iter_join(report.per_package.keys(), ", ")
d5538c38
AH
209 )
210 })?
6f185070 211 .to_string()
c2b02b39 212 } else {
d5538c38 213 report
315d605b 214 .per_package
d5538c38
AH
215 .values()
216 .cloned()
217 .collect::<Vec<_>>()
218 .join("\n")
219 };
6f185070
AH
220 to_display += &package_report;
221
622b43aa
DSW
222 let shell = config.shell();
223
224 let to_display = if shell.err_supports_color() && shell.out_supports_color() {
d5538c38
AH
225 to_display
226 } else {
227 strip_ansi_escapes::strip(&to_display)
c2b02b39
EH
228 .map(|v| String::from_utf8(v).expect("utf8"))
229 .expect("strip should never fail")
230 };
d5538c38 231 Ok(to_display)
c2b02b39
EH
232 }
233}
234
d5538c38
AH
235fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap<String, String> {
236 let mut report: BTreeMap<String, String> = BTreeMap::new();
237 for per_package in per_package_reports {
5afcce6b
AH
238 let package_spec = format!(
239 "{}:{}",
240 per_package.package_id.name(),
241 per_package.package_id.version()
242 );
243 let rendered = report.entry(package_spec).or_default();
c2b02b39 244 rendered.push_str(&format!(
7ffba677 245 "The package `{}` currently triggers the following future incompatibility lints:\n",
c2b02b39
EH
246 per_package.package_id
247 ));
248 for item in &per_package.items {
249 rendered.extend(
250 item.diagnostic
251 .rendered
252 .lines()
253 .map(|l| format!("> {}\n", l)),
254 );
255 }
71cb59b6 256 }
d5538c38 257 report
71cb59b6 258}
f57be6f6 259
5c3b3808
AH
260/// Returns a user-readable message explaining which of
261/// the packages in `package_ids` have updates available.
262/// This is best-effort - if an error occurs, `None` will be returned.
f57be6f6
AH
263fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet<PackageId>) -> Option<String> {
264 // This in general ignores all errors since this is opportunistic.
265 let _lock = ws.config().acquire_package_cache_lock().ok()?;
266 // Create a set of updated registry sources.
267 let map = SourceConfigMap::new(ws.config()).ok()?;
82093ad9 268 let mut package_ids: BTreeSet<_> = package_ids
f57be6f6
AH
269 .iter()
270 .filter(|pkg_id| pkg_id.source_id().is_registry())
271 .collect();
272 let source_ids: HashSet<_> = package_ids
273 .iter()
274 .map(|pkg_id| pkg_id.source_id())
275 .collect();
276 let mut sources: HashMap<_, _> = source_ids
277 .into_iter()
278 .filter_map(|sid| {
279 let source = map.load(sid, &HashSet::new()).ok()?;
280 Some((sid, source))
281 })
282 .collect();
82093ad9 283
f12f0256 284 // Query the sources for new versions, mapping `package_ids` into `summaries`.
82093ad9
AS
285 let mut summaries = Vec::new();
286 while !package_ids.is_empty() {
287 package_ids.retain(|&pkg_id| {
288 let source = match sources.get_mut(&pkg_id.source_id()) {
289 Some(s) => s,
290 None => return false,
291 };
292 let dep = match Dependency::parse(pkg_id.name(), None, pkg_id.source_id()) {
293 Ok(dep) => dep,
294 Err(_) => return false,
295 };
296 match source.query_vec(&dep) {
297 Poll::Ready(Ok(sum)) => {
298 summaries.push((pkg_id, sum));
299 false
300 }
301 Poll::Ready(Err(_)) => false,
302 Poll::Pending => true,
303 }
304 });
305 for (_, source) in sources.iter_mut() {
306 source.block_until_ready().ok()?;
307 }
308 }
309
f57be6f6 310 let mut updates = String::new();
82093ad9 311 for (pkg_id, summaries) in summaries {
f57be6f6
AH
312 let mut updated_versions: Vec<_> = summaries
313 .iter()
314 .map(|summary| summary.version())
315 .filter(|version| *version > pkg_id.version())
316 .collect();
317 updated_versions.sort();
318
319 let updated_versions = iter_join(
320 updated_versions
321 .into_iter()
322 .map(|version| version.to_string()),
323 ", ",
324 );
325
326 if !updated_versions.is_empty() {
327 writeln!(
328 updates,
329 "{} has the following newer versions available: {}",
330 pkg_id, updated_versions
331 )
332 .unwrap();
333 }
334 }
335 Some(updates)
336}
337
5c3b3808
AH
338/// Writes a future-incompat report to disk, using the per-package
339/// reports gathered during the build. If requested by the user,
340/// a message is also displayed in the build output.
341pub fn save_and_display_report(
f57be6f6
AH
342 bcx: &BuildContext<'_, '_>,
343 per_package_future_incompat_reports: &[FutureIncompatReportPackage],
344) {
f57be6f6
AH
345 let should_display_message = match bcx.config.future_incompat_config() {
346 Ok(config) => config.should_display_message(),
347 Err(e) => {
348 crate::display_warning_with_error(
349 "failed to read future-incompat config from disk",
350 &e,
351 &mut bcx.config.shell(),
352 );
353 true
354 }
355 };
356
357 if per_package_future_incompat_reports.is_empty() {
358 // Explicitly passing a command-line flag overrides
359 // `should_display_message` from the config file
360 if bcx.build_config.future_incompat_report {
361 drop(
362 bcx.config
363 .shell()
364 .note("0 dependencies had future-incompatible warnings"),
365 );
366 }
367 return;
368 }
369
958f2fc9
AH
370 let current_reports = match OnDiskReports::load(bcx.ws) {
371 Ok(r) => r,
372 Err(e) => {
373 log::debug!(
374 "saving future-incompatible reports failed to load current reports: {:?}",
375 e
376 );
377 OnDiskReports::default()
378 }
379 };
380 let report_id = current_reports.next_id;
381
f57be6f6
AH
382 // Get a list of unique and sorted package name/versions.
383 let package_ids: BTreeSet<_> = per_package_future_incompat_reports
384 .iter()
385 .map(|r| r.package_id)
386 .collect();
387 let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect();
388
389 if should_display_message || bcx.build_config.future_incompat_report {
390 drop(bcx.config.shell().warn(&format!(
391 "the following packages contain code that will be rejected by a future \
392 version of Rust: {}",
393 package_vers.join(", ")
394 )));
395 }
396
397 let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new());
398
399 let update_message = if !updated_versions.is_empty() {
400 format!(
401 "
402- Some affected dependencies have newer versions available.
403You may want to consider updating them to a newer version to see if the issue has been fixed.
404
405{updated_versions}\n",
406 updated_versions = updated_versions
407 )
408 } else {
409 String::new()
410 };
411
958f2fc9
AH
412 let upstream_info = package_ids
413 .iter()
414 .map(|package_id| {
415 let manifest = bcx.packages.get_one(*package_id).unwrap().manifest();
416 format!(
417 "
f57be6f6
AH
418 - {name}
419 - Repository: {url}
085f04ad 420 - Detailed warning command: `cargo report future-incompatibilities --id {id} --package {name}`",
958f2fc9
AH
421 name = format!("{}:{}", package_id.name(), package_id.version()),
422 url = manifest
423 .metadata()
424 .repository
425 .as_deref()
426 .unwrap_or("<not found>"),
427 id = report_id,
428 )
429 })
430 .collect::<Vec<_>>()
7ffba677 431 .join("\n");
958f2fc9
AH
432
433 let suggestion_message = format!(
7ffba677 434 "
f57be6f6
AH
435To solve this problem, you can try the following approaches:
436
437{update_message}
438- If the issue is not solved by updating the dependencies, a fix has to be
439implemented by those dependencies. You can help with that by notifying the
440maintainers of this problem (e.g. by creating a bug report) or by proposing a
441fix to the maintainers (e.g. by creating a pull request):
442{upstream_info}
443
444- If waiting for an upstream fix is not an option, you can use the `[patch]`
445section in `Cargo.toml` to use your own version of the dependency. For more
446information, see:
447https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section
448 ",
7ffba677
AH
449 upstream_info = upstream_info,
450 update_message = update_message,
958f2fc9
AH
451 );
452
958f2fc9
AH
453 current_reports.save_report(
454 bcx.ws,
455 suggestion_message.clone(),
456 per_package_future_incompat_reports,
457 );
458
459 if bcx.build_config.future_incompat_report {
460 drop(bcx.config.shell().note(&suggestion_message));
f57be6f6
AH
461 drop(bcx.config.shell().note(&format!(
462 "this report can be shown with `cargo report \
2a43df61 463 future-incompatibilities --id {}`",
f57be6f6
AH
464 report_id
465 )));
466 } else if should_display_message {
467 drop(bcx.config.shell().note(&format!(
468 "to see what the problems were, use the option \
469 `--future-incompat-report`, or run `cargo report \
470 future-incompatibilities --id {}`",
471 report_id
472 )));
473 }
474}