use std::{
env,
ffi::OsStr,
fs::{self, File},
io::Read,
path::Path,
process,
str,
};
use toml::Value;
use walkdir::WalkDir;
const EXPECTED_LICENSE_TEXT: &[u8] = include_bytes!(".resources/license_header");
const DIRS_TO_SKIP: [&str; 3] = ["examples", "js", "target"];
#[derive(Clone, Copy, PartialEq, Eq)]
enum ImportOfInterest {
Locktick,
ParkingLot,
Tokio,
}
fn check_locktick_imports<P: AsRef<Path>>(path: P) {
let mut iter = WalkDir::new(path).into_iter();
while let Some(entry) = iter.next() {
let entry = entry.unwrap();
let entry_type = entry.file_type();
if entry_type.is_dir() && DIRS_TO_SKIP.contains(&entry.file_name().to_str().unwrap_or("")) {
iter.skip_current_dir();
continue;
}
let path = entry.path();
if path.extension() != Some(OsStr::new("rs")) {
continue;
}
let file = fs::read_to_string(path).unwrap();
let lines = file
.lines()
.filter(|l| !l.is_empty()) .skip_while(|l| !l.starts_with("use")) .take_while(|l| { l.starts_with("use")
|| l.starts_with("#[cfg")
|| l.starts_with("//")
|| *l == "};"
|| l.starts_with(|c: char| c.is_ascii_whitespace())
});
let mut import_of_interest: Option<ImportOfInterest> = None;
let mut lock_balance: i8 = 0;
for line in lines {
if import_of_interest.is_none() {
if line.starts_with("use locktick::") {
import_of_interest = Some(ImportOfInterest::Locktick);
} else if line.starts_with("use parking_lot::") {
import_of_interest = Some(ImportOfInterest::ParkingLot);
} else if line.starts_with("use tokio::") {
import_of_interest = Some(ImportOfInterest::Tokio);
}
}
let Some(ioi) = import_of_interest else {
continue;
};
if [ImportOfInterest::ParkingLot, ImportOfInterest::Tokio].contains(&ioi) {
lock_balance += line.matches("Mutex").count() as i8;
lock_balance += line.matches("RwLock").count() as i8;
if line.contains("TMutex") {
lock_balance -= 1;
}
if line.contains("MutexGuard") {
lock_balance -= 1;
}
if line.contains("RwLockReadGuard") {
lock_balance -= 1;
}
if line.contains("RwLockWriteGuard") {
lock_balance -= 1;
}
} else if ioi == ImportOfInterest::Locktick {
lock_balance -= line.matches("Mutex").count() as i8;
lock_balance -= line.matches("RwLock").count() as i8;
if line.contains("TMutex") {
lock_balance += 1;
}
}
if line.ends_with(";") {
import_of_interest = None;
}
}
assert!(
lock_balance == 0,
"The locks in \"{}\" don't seem to have `locktick` counterparts!",
entry.path().display()
);
}
}
fn check_file_licenses<P: AsRef<Path>>(path: P) {
let path = path.as_ref();
if cfg!(target_os = "linux") {
let os_year = process::Command::new("date")
.arg("+%Y") .output()
.expect("Failed to execute 'date' command");
let current_year = str::from_utf8(&os_year.stdout).expect("Date output was not valid UTF-8").trim();
let license_year = str::from_utf8(&EXPECTED_LICENSE_TEXT[22..][..4]).unwrap();
assert_eq!(license_year, current_year, "The license year doesn't match the current OS year");
}
let mut iter = WalkDir::new(path).into_iter();
while let Some(entry) = iter.next() {
let entry = entry.unwrap();
let entry_type = entry.file_type();
if entry_type.is_dir() && entry.depth() == 1 && entry.file_name().to_str().is_some_and(|n| n.starts_with('.')) {
iter.skip_current_dir();
continue;
}
if entry_type.is_dir() && DIRS_TO_SKIP.contains(&entry.file_name().to_str().unwrap_or("")) {
iter.skip_current_dir();
continue;
}
if entry_type.is_file() && entry.file_name().to_str().unwrap_or("").ends_with(".rs") {
let file = File::open(entry.path()).unwrap();
let mut contents = Vec::with_capacity(EXPECTED_LICENSE_TEXT.len());
file.take(EXPECTED_LICENSE_TEXT.len() as u64).read_to_end(&mut contents).unwrap();
assert!(
contents == EXPECTED_LICENSE_TEXT,
"The license in \"{}\" is either missing or it doesn't match the expected string!",
entry.path().display()
);
}
}
}
fn check_locktick_profile() {
let locktick_enabled = env::var("CARGO_FEATURE_LOCKTICK").is_ok();
if locktick_enabled {
let (mut valid_debug_override, mut valid_strip_override) = (false, false);
if let Ok(val) = env::var("CARGO_PROFILE_RELEASE_DEBUG") {
if val != "line-tables-only" {
eprintln!(
"🔴 When enabling the locktick feature, CARGO_PROFILE_RELEASE_DEBUG may only be set to `line-tables-only`."
);
process::exit(1);
} else {
valid_debug_override = true;
}
}
if let Ok(val) = env::var("CARGO_PROFILE_RELEASE_STRIP") {
if val != "none" {
eprintln!(
"🔴 When enabling the locktick feature, CARGO_PROFILE_RELEASE_STRIP may only be set to `none`."
);
process::exit(1);
} else {
valid_strip_override = true;
}
}
if valid_debug_override && valid_strip_override {
return;
}
let profile = env::var("PROFILE").unwrap_or_else(|_| "".to_string());
let manifest = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("Cargo.toml");
let contents = fs::read_to_string(&manifest).expect("failed to read Cargo.toml");
let doc: Value = toml::from_str(&contents).expect("invalid TOML in Cargo.toml");
let profile_table = doc.get("profile").and_then(|p| p.get(profile));
if let Some(Value::Table(profile_settings)) = profile_table {
if let Some(debug) = profile_settings.get("debug") {
match debug {
Value::String(s) if s == "line-tables-only" => {
println!("cargo:info=manifest has debuginfo=line-tables-only");
}
_ => {
eprintln!(
"🔴 When enabling the locktick feature, the profile must have debug set to `line-tables-only`. Uncomment the relevant lines in Cargo.toml."
);
process::exit(1);
}
}
} else {
eprintln!(
"🔴 When enabling the locktick feature, the profile must have `debug` set to `line-tables-only`. Uncomment the relevant lines in Cargo.toml."
);
process::exit(1);
}
if let Some(debug) = profile_settings.get("strip") {
match debug {
Value::String(s) if s == "none" => {
println!("cargo:info=manifest has strip=none");
}
_ => {
eprintln!(
"🔴 When enabling the locktick feature, the profile must have `strip` set to `none`. Uncomment the relevant lines in Cargo.toml."
);
process::exit(1);
}
}
}
}
}
}
fn is_clippy() -> bool {
env::var("RUSTC_WORKSPACE_WRAPPER").is_ok_and(|var| var.contains("clippy"))
}
fn check_tokio_console_flags() {
if is_clippy() {
return;
}
let feature_enabled = env::var("CARGO_FEATURE_TOKIO_CONSOLE").is_ok();
if !feature_enabled {
return;
}
let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") else {
eprintln!(
"🔴 When enabling the tokio_console feature, you must run with `RUSTFLAGS=\"--cfg tokio_unstable\"`."
);
process::exit(1);
};
if !rustflags.contains("tokio_unstable") {
eprintln!(
"🔴 When enabling the tokio_console feature, you must run with `RUSTFLAGS=\"--cfg tokio_unstable\"`."
);
process::exit(1);
}
}
fn emit_version_env() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("Failed to read CARGO_MANIFEST_DIR");
let release_version = Path::new(&manifest_dir)
.ancestors()
.map(|dir| dir.join(".cargo").join("release-version"))
.find(|path| path.is_file());
let version = match &release_version {
Some(path) => {
println!("cargo:rerun-if-changed={}", path.display());
fs::read_to_string(path).unwrap_or_else(|err| panic!("Failed to read '{}': {err}", path.display()))
}
None => env::var("CARGO_PKG_VERSION").expect("Failed to read CARGO_PKG_VERSION"),
};
let version = version.trim();
let version = version.strip_prefix('v').unwrap_or(version);
assert!(!version.is_empty(), "The snarkOS version is empty");
println!("cargo:rustc-env=SNARKOS_VERSION={version}");
}
fn main() {
check_file_licenses(".");
check_locktick_imports(".");
check_locktick_profile();
check_tokio_console_flags();
emit_version_env();
built::write_built_file().expect("Failed to acquire build-time information");
}