// Copyright 2024 The Fuchsia Authors // // Licensed under the 2-Clause BSD License , Apache License, Version 2.0 // , or the MIT // license , 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 { 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 } }