Skip to main content

xtask/tasks/fmt/lints/
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, edition, and
6//! fields are properly workspaced.
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 exceptions in [`VERSION_EXCEPTIONS`] are the crates where the version is
16//! *not* meaningless, because something outside the crate reads it. Those
17//! crates lose the publishing protection described above, so each one sets
18//! `publish = false` explicitly instead.
19//!
20//! The [authors][] field is optional, is not really used anywhere anymore, and
21//! just creates confusion.
22//!
23//! [version]:
24//!     <https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field>
25//! [authors]:
26//!     <https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field>
27
28use super::Lint;
29use super::LintCtx;
30use super::Lintable;
31use toml_edit::DocumentMut;
32use toml_edit::Item;
33use toml_edit::Table;
34
35/// List of packages that are allowed to have a version.
36///
37/// Each of these must also set `publish = false`, since carrying a version
38/// forfeits the implicit publishing protection described at the module level.
39///
40/// `vmgstool` publishes a versioned binary, and its release tag is built from
41/// the version in its manifest.
42///
43/// `openvmm` and `openvmm_build_info` carry the workspace version because
44/// OpenVMM must retain its identity in a Git-free source tree. The version has
45/// to travel inside the source, since a downstream builder cannot recover it
46/// from Git metadata. Cargo identifies the product package with this version,
47/// and `openvmm_build_info` exposes the corresponding CLI identity.
48///
49/// If this list grows much past a handful, replace it with a
50/// `package.metadata.xtask.house-rules` opt-in, the way `excluded-from-workspace`
51/// below already works -- keeping the policy next to the crate it applies to.
52static VERSION_EXCEPTIONS: &[&str] = &["openvmm", "openvmm_build_info", "vmgstool"];
53
54pub struct PackageInfo;
55
56impl Lint for PackageInfo {
57    fn new(_ctx: &LintCtx) -> Self {
58        PackageInfo
59    }
60
61    fn enter_workspace(&mut self, _content: &Lintable<DocumentMut>) {}
62    fn enter_crate(&mut self, _content: &Lintable<DocumentMut>) {}
63    fn visit_file(&mut self, _content: &mut Lintable<String>) {}
64
65    fn exit_crate(&mut self, content: &mut Lintable<DocumentMut>) {
66        let package = content["package"].as_table().unwrap();
67        let excluded_from_workspace = package
68            .get("metadata")
69            .and_then(|x| x.get("xtask"))
70            .and_then(|x| x.get("house-rules"))
71            .and_then(|x| x.get("excluded-from-workspace"))
72            .and_then(|v| v.as_bool())
73            .unwrap_or(false);
74
75        let package_name = package["name"].as_str().unwrap();
76        let is_version_exception = VERSION_EXCEPTIONS.contains(&package_name);
77        let check_version = !is_version_exception;
78
79        let mut lints_table = Table::new();
80        lints_table.insert("workspace", Item::Value(true.into()));
81
82        let mut rust_version_field = Table::new();
83        rust_version_field.set_dotted(true);
84        rust_version_field.insert("workspace", Item::Value(true.into()));
85
86        let mut edition_field = Table::new();
87        edition_field.set_dotted(true);
88        edition_field.insert("workspace", Item::Value(true.into()));
89
90        let has_authors = package.contains_key("authors");
91        let has_version = check_version && package.contains_key("version");
92        let needs_publish_false =
93            is_version_exception && package.get("publish").and_then(Item::as_bool) != Some(false);
94        let needs_lints_fix = !excluded_from_workspace
95            && content.get("lints").map(|o| o.to_string()).as_deref()
96                != Some(&lints_table.to_string());
97        let needs_rust_version_fix = !excluded_from_workspace
98            && package
99                .get("rust-version")
100                .map(|o| o.to_string())
101                .as_deref()
102                != Some(&rust_version_field.to_string());
103        let needs_edition_fix = !excluded_from_workspace
104            && package.get("edition").map(|o| o.to_string()).as_deref()
105                != Some(&edition_field.to_string());
106
107        if has_authors {
108            content.fix("package should not have authors field", |doc| {
109                doc["package"].as_table_mut().unwrap().remove("authors");
110            });
111        }
112
113        if has_version {
114            content.fix("package should not have version field", |doc| {
115                doc["package"].as_table_mut().unwrap().remove("version");
116            });
117        }
118
119        if needs_publish_false {
120            content.fix("versioned package should set publish = false", |doc| {
121                doc["package"]
122                    .as_table_mut()
123                    .unwrap()
124                    .insert("publish", Item::Value(false.into()));
125            });
126        }
127
128        if needs_lints_fix {
129            content.fix("lints should be workspaced", |doc| {
130                doc.insert("lints", Item::Table(lints_table));
131            });
132        }
133
134        if needs_rust_version_fix {
135            content.fix("rust-version should be workspaced", |doc| {
136                doc["package"]
137                    .as_table_mut()
138                    .unwrap()
139                    .insert("rust-version", Item::Table(rust_version_field));
140            });
141        }
142
143        if needs_edition_fix {
144            content.fix("edition should be workspaced", |doc| {
145                doc["package"]
146                    .as_table_mut()
147                    .unwrap()
148                    .insert("edition", Item::Table(edition_field));
149            });
150        }
151    }
152
153    fn exit_workspace(&mut self, _content: &mut Lintable<DocumentMut>) {}
154}