xtask/tasks/fmt/house_rules/
package_info.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Checks to ensure that the `[package]` sections of Cargo.toml files do not
5//! contain `authors` or `version` fields, and that rust-version is properly
6//! workspace.
7//!
8//! Eliding the [version][] sets the version to "0.0.0", which is fine. More
9//! importantly, it means the module cannot be published to crates.io
10//! (equivalent to publish = false), which is what we want for our internal
11//! crates. And removing the meaningless version also eliminates more questions
12//! from newcomers (does the version field mean anything? do we use it for
13//! semver internally?).
14//!
15//! The [authors][] field is optional, is not really used anywhere anymore, and
16//! just creates confusion.
17//!
18//! [version]:
19//!     <https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field>
20//! [authors]:
21//!     <https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field>
22
23use anyhow::Context;
24use std::ffi::OsStr;
25use std::path::Path;
26use toml_edit::Item;
27use toml_edit::Table;
28
29/// List of packages that are allowed to have a version
30static VERSION_EXCEPTIONS: &[&str] = &["vmgstool"];
31
32pub fn check_package_info(f: &Path, fix: bool) -> anyhow::Result<()> {
33    if f.file_name() != Some(OsStr::new("Cargo.toml")) {
34        return Ok(());
35    }
36
37    let contents = fs_err::read_to_string(f)?;
38    let mut parsed = contents.parse::<toml_edit::DocumentMut>()?;
39
40    let mut excluded_from_workspace = false;
41    if let Some(metadata) = parsed
42        .get("package")
43        .and_then(|x| x.get("metadata"))
44        .and_then(|x| x.get("xtask"))
45        .and_then(|x| x.get("house-rules"))
46    {
47        let props = metadata.as_table().context("invalid metadata format")?;
48        for (k, v) in props.iter() {
49            if k == "excluded-from-workspace" {
50                excluded_from_workspace = v
51                    .as_bool()
52                    .context("invalid type for excluded-from-workspace (must be bool)")?;
53            }
54        }
55    }
56
57    if !parsed.contains_key("package") {
58        // workspace root, skip
59        return Ok(());
60    }
61
62    let mut lints_table = Table::new();
63    lints_table.insert("workspace", Item::Value(true.into()));
64    let old_lints_table = parsed.insert("lints", Item::Table(lints_table.clone()));
65
66    let package = parsed
67        .get_mut("package")
68        .with_context(|| format!("missing package section in {}", f.display()))?
69        .as_table_mut()
70        .with_context(|| format!("invalid package section in {}", f.display()))?;
71
72    let mut rust_version_field = Table::new();
73    rust_version_field.set_dotted(true);
74    rust_version_field.insert("workspace", Item::Value(true.into()));
75    let old_rust_version = package.insert("rust-version", Item::Table(rust_version_field.clone()));
76
77    let mut edition_field = Table::new();
78    edition_field.set_dotted(true);
79    edition_field.insert("workspace", Item::Value(true.into()));
80    let old_edition_field = package.insert("edition", Item::Table(edition_field.clone()));
81
82    let package_name = package
83        .get("name")
84        .with_context(|| format!("missing package name in {}", f.display()))?
85        .as_str()
86        .with_context(|| format!("invalid package name in {}", f.display()))?;
87    let check_version = !VERSION_EXCEPTIONS.contains(&package_name);
88
89    // Note careful use of non-short-circuiting or.
90    let invalid = package.remove("authors").is_some()
91        | check_version
92            .then(|| package.remove("version"))
93            .flatten()
94            .is_some()
95        | (!excluded_from_workspace
96            && (old_lints_table.map(|o| o.to_string()) != Some(lints_table.to_string()))
97                | (old_rust_version.map(|o| o.to_string())
98                    != Some(rust_version_field.to_string()))
99                | (old_edition_field.map(|o| o.to_string()) != Some(edition_field.to_string())));
100
101    if invalid {
102        if !fix {
103            anyhow::bail!(
104                "invalid inclusion of package authors or version, or non-workspaced lints, rust-version, or edition, in {}",
105                f.display()
106            );
107        }
108        fs_err::write(f, parsed.to_string())?;
109        log::info!("fixed package section in {}", f.display());
110    }
111
112    Ok(())
113}