1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Copyright 2024 The Fuchsia Authors
//
// Licensed under the 2-Clause BSD License <LICENSE-BSD or
// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.
// Sometimes we want to use lints which were added after our MSRV.
// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
// this attribute, any unknown lint would cause a CI failure when testing with
// our MSRV.
#![allow(unknown_lints)]
#![deny(renamed_and_removed_lints)]
#![deny(
anonymous_parameters,
deprecated_in_future,
late_bound_lifetime_arguments,
missing_copy_implementations,
missing_debug_implementations,
path_statements,
patterns_in_fns_without_body,
rust_2018_idioms,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_extern_crates,
variant_size_differences
)]
#![deny(
clippy::all,
clippy::alloc_instead_of_core,
clippy::arithmetic_side_effects,
clippy::as_underscore,
clippy::assertions_on_result_states,
clippy::as_conversions,
clippy::correctness,
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::get_unwrap,
clippy::indexing_slicing,
clippy::missing_inline_in_public_items,
clippy::missing_safety_doc,
clippy::obfuscated_if_else,
clippy::perf,
clippy::print_stdout,
clippy::style,
clippy::suspicious,
clippy::todo,
clippy::undocumented_unsafe_blocks,
clippy::unimplemented,
clippy::unnested_or_patterns,
clippy::unwrap_used,
clippy::use_debug
)]
use std::{env, fs, process::Command, str};
fn main() {
// Avoid unnecessary re-building.
println!("cargo:rerun-if-changed=build.rs");
// This is necessary because changes to the list of detected Rust toolchain
// versions will affect what `--cfg`s this script emits. Without this,
// changes to that list have no effect on the build without running `cargo
// clean` or similar.
println!("cargo:rerun-if-changed=Cargo.toml");
let version_cfgs = parse_version_cfgs_from_cargo_toml();
let rustc_version = rustc_version();
if rustc_version >= (Version { major: 1, minor: 77, patch: 0 }) {
// This tells the `unexpected_cfgs` lint to expect to see all of these
// `cfg`s. The `cargo::` syntax was only added in 1.77, so we don't emit
// these on earlier toolchain versions.
for version_cfg in &version_cfgs {
println!("cargo:rustc-check-cfg=cfg({})", version_cfg.cfg_name);
}
// TODO(https://github.com/rust-lang/rust/issues/124816): Remove these
// once they're no longer needed.
println!("cargo:rustc-check-cfg=cfg(doc_cfg)");
println!("cargo:rustc-check-cfg=cfg(kani)");
println!(
"cargo:rustc-check-cfg=cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)"
);
println!("cargo:rustc-check-cfg=cfg(coverage_nightly)");
}
for version_cfg in version_cfgs {
if rustc_version >= version_cfg.version {
println!("cargo:rustc-cfg={}", version_cfg.cfg_name);
}
}
}
#[derive(Debug, Ord, PartialEq, PartialOrd, Eq)]
struct Version {
major: usize,
minor: usize,
patch: usize,
}
#[derive(Debug)]
struct VersionCfg {
version: Version,
cfg_name: String,
}
const ITER_FIRST_NEXT_EXPECT_MSG: &str = "unreachable: a string split cannot produce 0 items";
fn parse_version_cfgs_from_cargo_toml() -> Vec<VersionCfg> {
let cargo_toml = fs::read_to_string("Cargo.toml").expect("failed to read Cargo.toml");
// Expect a Cargo.toml with the following format:
//
// ...
//
// [package.metadata.build-rs]
// # Comments...
// zerocopy-panic-in-const-fn = "1.57.0"
//
// ...
//
// [...]
//
// In other words, the following sections, in order:
// - Arbitrary content
// - The literal header `[package.metadata.build-rs]`
// - Any number of:
// - Comments
// - Key/value pairs
// - A TOML table, indicating the end of the section we care about
const TABLE_HEADER: &str = "[package.metadata.build-rs]";
if !cargo_toml.contains(TABLE_HEADER) {
panic!("{}", format!("Cargo.toml does not contain `{}`", TABLE_HEADER));
}
// Now that we know there's at least one instance of `TABLE_HEADER`, we
// consume the iterator until we find the text following that first
// instance. This isn't terribly bullet-proof, but we also authored
// `Cargo.toml`, and we'd have to mess up pretty badly to accidentally put
// two copies of the same table header in that file.
let mut iter = cargo_toml.split(TABLE_HEADER);
let _prefix = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
let rest = iter.next().expect("unreachable: we already confirmed that there's a table header");
// Scan until we find the next table section, which should start with a `[`
// character at the beginning of a line.
let mut iter = rest.split("\n[");
let section = iter.next().expect("unreachable: a string split cannot produce 0 items");
section
.lines()
.filter_map(|line| {
// Parse lines of one of the following forms:
//
// # Comment
//
// name-of-key = "1.2.3" # Comment
//
// Comments on their own line are ignored, and comments after a
// key/value pair will be stripped before further processing.
// We don't need to handle the case where the `#` character isn't a
// comment (which can happen if it's inside a string) since we authored
// `Cargo.toml` and, in this section, we only put Rust version numbers
// in strings.
let before_comment = line.split('#').next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
let before_comment_without_whitespace = before_comment.trim_start();
if before_comment_without_whitespace.is_empty() {
return None;
}
// At this point, assuming Cargo.toml is correctly formatted according
// to the format expected by this function, we know that
// `before_comment_without_whitespace` is of the form:
//
// name-of-key = "1.2.3" # Comment
//
// ...with no leading whitespace, and where the trailing comment is
// optional.
let mut iter = before_comment_without_whitespace.split_whitespace();
let name = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
const EXPECT_MSG: &str =
"expected lines of the format `name-of-key = \"1.2.3\" # Comment`";
let equals_sign = iter.next().expect(EXPECT_MSG);
let value = iter.next().expect(EXPECT_MSG);
assert_eq!(equals_sign, "=", "{}", EXPECT_MSG);
// Replace dashes with underscores.
let name = name.replace('-', "_");
// Strip the quotation marks.
let value = value.trim_start_matches('"').trim_end_matches('"');
let mut iter = value.split('.');
let major = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
let minor = iter.next().expect(EXPECT_MSG);
let patch = iter.next().expect(EXPECT_MSG);
assert_eq!(iter.next(), None, "{}", EXPECT_MSG);
let major: usize = major.parse().expect(EXPECT_MSG);
let minor: usize = minor.parse().expect(EXPECT_MSG);
let patch: usize = patch.parse().expect(EXPECT_MSG);
Some(VersionCfg { version: Version { major, minor, patch }, cfg_name: name })
})
.collect()
}
fn rustc_version() -> Version {
let rustc_cmd_name = env::var_os("RUSTC").expect("could not get rustc command name");
let version =
Command::new(rustc_cmd_name).arg("--version").output().expect("could not invoke rustc");
if !version.status.success() {
panic!(
"rustc failed with status: {}\nrustc output: {}",
version.status,
String::from_utf8_lossy(version.stderr.as_slice())
);
}
const RUSTC_EXPECT_MSG: &str = "could not parse rustc version output";
let version = str::from_utf8(version.stdout.as_slice()).expect(RUSTC_EXPECT_MSG);
let version = version.trim_start_matches("rustc ");
// The version string is sometimes followed by other information such as the
// string `-nightly` or other build information. We don't care about any of
// that.
let version = version
.split(|c: char| c != '.' && !c.is_ascii_digit())
.next()
.expect(ITER_FIRST_NEXT_EXPECT_MSG);
let mut iter = version.split('.');
let major = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
let minor = iter.next().expect(RUSTC_EXPECT_MSG);
let patch = iter.next().expect(RUSTC_EXPECT_MSG);
let major: usize = major.parse().expect(RUSTC_EXPECT_MSG);
let minor: usize = minor.parse().expect(RUSTC_EXPECT_MSG);
let patch: usize = patch.parse().expect(RUSTC_EXPECT_MSG);
Version { major, minor, patch }
}