#![allow( clippy::disallowed_methods, reason = "build scripts cannot use rustpython-host_env" )] use jiff::{Timestamp, Zoned, tz::TimeZone}; use std::{ env, io, process::Command, time::{SystemTime, UNIX_EPOCH}, }; fn main() { let frozen_libs = if cfg!(feature = "freeze-stdlib") { "Lib/*/*.py" } else { "Lib/python_builtins/*.py" }; for entry in glob::glob(frozen_libs).expect("Lib/ exists?").flatten() { let display = entry.display(); println!("cargo:rerun-if-changed={display}"); } println!("cargo:rerun-if-changed=../../Lib/importlib/_bootstrap.py"); // = 3.15.0alpha python_version(3, 15, 0, "alpha", 0); println!("cargo:rustc-env=RUSTPYTHON_GIT_HASH={}", git_hash()); println!( "cargo:rustc-env=RUSTPYTHON_GIT_TIMESTAMP={}", git_timestamp() ); println!("cargo:rustc-env=RUSTPYTHON_GIT_TAG={}", git_tag()); println!("cargo:rustc-env=RUSTPYTHON_GIT_BRANCH={}", git_branch()); println!( "cargo:rustc-env=RUSTPYTHON_GIT_IDENTIFIER={}", git_identifier() ); println!("cargo:rustc-env=RUSTPYTHON_BUILD_INFO={}", get_build_info()); println!("cargo:rustc-env=RUSTC_VERSION={}", rustc_version()); let release_level = option_env!("RUSTPYTHON_RELEASE_LEVEL").unwrap_or("alpha"); println!("cargo:rustc-env=RUSTPYTHON_RELEASE_LEVEL={release_level}"); let release_level_n = release_to_n(release_level); println!("cargo:rustc-env=RUSTPYTHON_RELEASE_LEVEL_N={release_level_n}"); let release_serial = option_env!("RUSTPYTHON_RELEASE_SERIAL").unwrap_or("0"); println!("cargo:rustc-env=RUSTPYTHON_RELEASE_SERIAL={release_serial}"); println!( "cargo:rustc-env=RUSTPYTHON_TARGET_TRIPLE={}", env::var("TARGET").unwrap() ); } fn git_hash() -> String { git(&["rev-parse", "--short", "HEAD"]).unwrap_or_else(|_| "0000000".into()) } fn git_timestamp() -> String { git(&["log", "-1", "--format=%ct"]).unwrap_or_else(|_| { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() .to_string() }) } fn git_tag() -> String { git(&["describe", "--all", "--always", "--dirty"]) .unwrap_or_else(|_| "heads/unknown-branch".into()) } fn git_branch() -> String { git(&["name-rev", "--name-only", "HEAD"]).unwrap_or_else(|_| "unknown-branch".into()) } fn git(args: &[&str]) -> io::Result { command("git", args) } #[must_use] fn get_build_info() -> String { // See: https://reproducible-builds.org/docs/timestamps/ let revision = git_hash(); let separator = if revision.is_empty() { "" } else { ":" }; let identifier = git_identifier(); format!( "{id}{sep}{revision}, {date:.20}, {time:.9}", id = if identifier.is_empty() { "default" } else { &identifier }, sep = separator, revision = revision, date = get_git_date(), time = get_git_time(), ) } fn git_identifier() -> String { let tag = git_tag(); if tag.is_empty() || tag.eq_ignore_ascii_case("undefined") { git_branch() } else { tag } } fn get_git_timestamp_raw() -> Option { let git_timestamp = git_timestamp().parse::().ok()?; Timestamp::from_second(git_timestamp).ok() } fn get_git_timestamp_datetime() -> Zoned { get_git_timestamp_raw().map_or_else(Zoned::now, |timestamp| { Zoned::new(timestamp, TimeZone::system()) }) } #[must_use] fn get_git_date() -> String { let datetime = get_git_timestamp_datetime(); datetime.strftime("%b %e %Y").to_string() } #[must_use] fn get_git_time() -> String { let datetime = get_git_timestamp_datetime(); datetime.strftime("%H:%M:%S").to_string() } fn rustc_version() -> String { let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); command(rustc, &["-V"]).unwrap_or_else(|_| "rustc [unknown]".into()) } fn python_version(major: usize, minor: usize, micro: usize, release: &str, serial: usize) { println!("cargo:rustc-env=MAJOR_CPY={major}"); println!("cargo:rustc-env=MINOR_CPY={minor}"); println!("cargo:rustc-env=MICRO_CPY={micro}"); println!("cargo:rustc-env=RELEASE_LEVEL_CPY={release}"); println!( "cargo:rustc-env=RELEASE_LEVEL_N_CPY={}", release_to_n(release) ); println!("cargo:rustc-env=SERIAL_CPY={serial}"); println!("cargo:rustc-env=WINVER_CPY={major}.{minor}",); let cpy_version = format!("{major}.{minor}.{micro}.{release}"); let (left, right) = get_version(&cpy_version); println!("cargo:rustc-env=RUSTPYTHON_VERSION_LEFT={left}"); println!("cargo:rustc-env=RUSTPYTHON_VERSION_RIGHT={right}"); } #[must_use] fn get_version(cpy_version: &str) -> (String, String) { // Windows: include MSC v. for compatibility with ctypes.util.find_library // MSC v.1929 = VS 2019, version 14+ makes find_msvcrt() return None let msc_info = cfg_select! { windows => {{ // Include both RustPython identifier and MSC v. for compatibility if cfg!(target_pointer_width = "64") { " MSC v.1929 64 bit (AMD64)" } else { " MSC v.1929 32 bit (Intel)" } }}, _ => "", }; // `left` and `right` are split by \n like PyPy. Passing a string with a newline to rustc // truncates everything from the newline onward, so we have to manually combine them later. let left = format!("{:.80} ({:.80})", cpy_version, get_build_info()); let right = format!( "[RustPython {} with {:.80}{}]", env!("CARGO_PKG_VERSION"), rustc_version(), msc_info, ); (left, right) } fn release_to_n(release: &str) -> usize { match release { "alpha" => 0xA, "beta" => 0xB, "candidate" => 0xC, "final" => 0xD, _ => panic!("`release` must be one of: 'alpha', 'beta', 'candidate', 'final'"), } } fn command(cmd: impl AsRef, args: &[&str]) -> io::Result { Command::new(&cmd).args(args).output().and_then(|output| { // TODO: Switch to exit_ok()? when stable. if !output.status.success() { Err(io::Error::other(format!( "command '{}' exited with status {}", cmd.as_ref().to_string_lossy(), output.status )))? } str::from_utf8(&output.stdout) .map(|s| s.trim().to_owned()) .map_err(io::Error::other) }) }