diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 7287ca549fd..b8acf23a2c0 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -99,6 +99,7 @@ widestring = { workspace = true } wasm-bindgen = { workspace = true, optional = true } [build-dependencies] +chrono = { workspace = true } glob = { workspace = true } itertools = { workspace = true } diff --git a/crates/vm/build.rs b/crates/vm/build.rs index 6c65aa8633b..36e7a5d9d27 100644 --- a/crates/vm/build.rs +++ b/crates/vm/build.rs @@ -3,6 +3,8 @@ reason = "build scripts cannot use rustpython-host_env" )] +use chrono::{Local, prelude::DateTime}; +use core::time::Duration; use itertools::Itertools; use std::{ env, @@ -24,6 +26,9 @@ fn main() { } println!("cargo:rerun-if-changed=../../Lib/importlib/_bootstrap.py"); + // = 3.14.0alpha + python_version(3, 14, 0, "alpha", 0); + println!("cargo:rustc-env=RUSTPYTHON_GIT_HASH={}", git_hash()); println!( "cargo:rustc-env=RUSTPYTHON_GIT_TIMESTAMP={}", @@ -31,10 +36,17 @@ fn main() { ); 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}"); @@ -81,11 +93,119 @@ 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_datetime() -> DateTime { + let timestamp = git_timestamp().parse::().unwrap_or_default(); + let datetime = UNIX_EPOCH + Duration::from_secs(timestamp); + datetime.into() +} + +#[must_use] +fn get_git_date() -> String { + let datetime = get_git_timestamp_datetime(); + + datetime.format("%b %e %Y").to_string() +} + +#[must_use] +fn get_git_time() -> String { + let datetime = get_git_timestamp_datetime(); + + datetime.format("%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. diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 72fa1f6432b..44e692f1783 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -649,7 +649,7 @@ pub mod sys { fn _git(vm: &VirtualMachine) -> PyTupleRef { vm.new_tuple(( ascii!("RustPython"), - version::get_git_identifier(), + version::GIT_IDENTIFIER, version::GIT_REVISION, )) } @@ -706,17 +706,13 @@ pub mod sys { vm.ctx.none() } - #[pyattr] - fn version(_vm: &VirtualMachine) -> String { - version::get_version() - } + #[pyattr(name = "version")] + const VERSION: &str = version::RUSTPYTHON_VERSION; + // Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility #[cfg(windows)] - #[pyattr] - fn winver(_vm: &VirtualMachine) -> String { - // Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility - version::get_winver_number() - } + #[pyattr(name = "winver")] + const WINVER: &str = version::WINVER; #[pyattr] fn _xoptions(vm: &VirtualMachine) -> PyDictRef { diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 29ed3c2aa4a..f30a6a49426 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -1,145 +1,72 @@ -//! Several function to retrieve version information. - -use chrono::{Local, prelude::DateTime}; -use core::time::Duration; -use std::time::UNIX_EPOCH; +//! Version info constants. +//! +//! Most of the constants are auto calculated at compile time. The main exception is the +//! target CPython version. This is defined and updated in `build.rs`. + +macro_rules! parse_consts { + ($name: ident, $var: literal) => { + pub const $name: usize = match usize::from_str_radix(env!($var), 10) { + Ok(v) => v, + Err(_) => panic!(concat!("Compile with Cargo to get '", $var, "'")), + }; + }; +} -// = 3.14.0alpha -pub const MAJOR: usize = 3; -pub const MINOR: usize = 14; -pub const MICRO: usize = 0; -pub const RELEASELEVEL: &str = "alpha"; -pub const RELEASELEVEL_N: usize = 0xA; -pub const SERIAL: usize = 0; +// CPython target version info +parse_consts!(MAJOR, "MAJOR_CPY"); +parse_consts!(MINOR, "MINOR_CPY"); +parse_consts!(MICRO, "MICRO_CPY"); +pub const RELEASELEVEL: &str = env!("RELEASE_LEVEL_CPY"); +parse_consts!(RELEASELEVEL_N, "RELEASE_LEVEL_N_CPY"); +parse_consts!(SERIAL, "SERIAL_CPY"); pub const VERSION_HEX: usize = (MAJOR << 24) | (MINOR << 16) | (MICRO << 8) | (RELEASELEVEL_N << 4) | SERIAL; +#[cfg(windows)] +pub const WINVER: &str = env!("WINVER_CPY"); + pub const GIT_REVISION: &str = env!("RUSTPYTHON_GIT_HASH"); -const GIT_TAG: &str = env!("RUSTPYTHON_GIT_TAG"); -const GIT_BRANCH: &str = env!("RUSTPYTHON_GIT_BRANCH"); +pub const GIT_IDENTIFIER: &str = env!("RUSTPYTHON_GIT_IDENTIFIER"); +// const GIT_TAG: &str = env!("RUSTPYTHON_GIT_TAG"); +// const GIT_BRANCH: &str = env!("RUSTPYTHON_GIT_BRANCH"); // RustPython version -pub const MAJOR_IMPL: usize = match usize::from_str_radix(env!("CARGO_PKG_VERSION_MAJOR"), 10) { - Ok(v) => v, - Err(_) => panic!("Compile with Cargo to get 'CARGO_PKG_VERSION_MAJOR'"), -}; -pub const MINOR_IMPL: usize = match usize::from_str_radix(env!("CARGO_PKG_VERSION_MINOR"), 10) { - Ok(v) => v, - Err(_) => panic!("Compile with Cargo to get 'CARGO_PKG_VERSION_MINOR'"), -}; -pub const MICRO_IMPL: usize = match usize::from_str_radix(env!("CARGO_PKG_VERSION_PATCH"), 10) { - Ok(v) => v, - Err(_) => panic!("Compile with Cargo to get 'CARGO_PKG_VERSION_PATCH'"), -}; +parse_consts!(MAJOR_IMPL, "CARGO_PKG_VERSION_MAJOR"); +parse_consts!(MINOR_IMPL, "CARGO_PKG_VERSION_MINOR"); +parse_consts!(MICRO_IMPL, "CARGO_PKG_VERSION_PATCH"); pub const RELEASELEVEL_IMPL: &str = env!("RUSTPYTHON_RELEASE_LEVEL"); -pub const SERIAL_IMPL: usize = match usize::from_str_radix(env!("RUSTPYTHON_RELEASE_SERIAL"), 10) { - Ok(v) => v, - Err(_) => panic!("Compile with Cargo to get 'RUSTPYTHON_RELEASE_SERIAL'"), -}; +parse_consts!(RELEASELEVEL_N_IMPL, "RUSTPYTHON_RELEASE_LEVEL_N"); +parse_consts!(SERIAL_IMPL, "RUSTPYTHON_RELEASE_SERIAL"); pub const VERSION_HEX_IMPL: usize = (MAJOR_IMPL << 24) | (MINOR_IMPL << 16) | (MICRO_IMPL << 8) - | (RELEASELEVEL_N << 4) + | (RELEASELEVEL_N_IMPL << 4) | SERIAL_IMPL; -#[must_use] -pub fn get_version() -> 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 => {{ - let arch = if cfg!(target_pointer_width = "64") { - "64 bit (AMD64)" - } else { - "32 bit (Intel)" - }; - // Include both RustPython identifier and MSC v. for compatibility - format!(" MSC v.1929 {arch}",) - }}, - _ => String::new(), - }; - - format!( - "{:.80} ({:.80}) \n[RustPython {} with {:.80}{}]", // \n is PyPy convention - get_version_number(), - get_build_info(), - env!("CARGO_PKG_VERSION"), - COMPILER, - msc_info, - ) -} - -#[must_use] -pub fn get_version_number() -> String { - format!("{MAJOR}.{MINOR}.{MICRO}{RELEASELEVEL}") -} - -#[must_use] -pub fn get_winver_number() -> String { - format!("{MAJOR}.{MINOR}") -} - -const COMPILER: &str = env!("RUSTC_VERSION"); +pub const RUSTPYTHON_BUILD_INFO: &str = env!("RUSTPYTHON_BUILD_INFO"); +pub const RUSTPYTHON_VERSION: &str = const { + const LEFT: &str = env!("RUSTPYTHON_VERSION_LEFT"); + const RIGHT: &str = env!("RUSTPYTHON_VERSION_RIGHT"); + const LEN: usize = LEFT.len() + RIGHT.len() + 1; -#[must_use] -pub fn get_build_info() -> String { - // See: https://reproducible-builds.org/docs/timestamps/ - let separator = if GIT_REVISION.is_empty() { "" } else { ":" }; - let git_identifier = get_git_identifier(); + const fn concat() -> [u8; LEN] { + let mut bytes_temp = [0u8; LEN]; - format!( - "{id}{sep}{revision}, {date:.20}, {time:.9}", - id = if git_identifier.is_empty() { - "default" - } else { - git_identifier - }, - sep = separator, - revision = GIT_REVISION, - date = get_git_date(), - time = get_git_time(), - ) -} + let (left, _) = bytes_temp.split_at_mut(LEFT.len()); + left.copy_from_slice(LEFT.as_bytes()); + let (_, right) = bytes_temp.split_at_mut(LEFT.len() + 1); + right.copy_from_slice(RIGHT.as_bytes()); + bytes_temp[LEFT.len()] = b'\n'; -#[must_use] -pub const fn get_git_identifier() -> &'static str { - if GIT_TAG.is_empty() || GIT_TAG.eq_ignore_ascii_case("undefined") { - GIT_BRANCH - } else { - GIT_TAG + bytes_temp } -} - -fn get_git_timestamp_datetime() -> DateTime { - let timestamp = option_env!("RUSTPYTHON_GIT_TIMESTAMP").unwrap_or_default(); - let timestamp = timestamp.parse::().unwrap_or_default(); - - let datetime = UNIX_EPOCH + Duration::from_secs(timestamp); - - datetime.into() -} - -#[must_use] -pub fn get_git_date() -> String { - let datetime = get_git_timestamp_datetime(); - - datetime.format("%b %e %Y").to_string() -} - -#[must_use] -pub fn get_git_time() -> String { - let datetime = get_git_timestamp_datetime(); - - datetime.format("%H:%M:%S").to_string() -} -#[must_use] -pub fn get_git_datetime() -> String { - let date = get_git_date(); - let time = get_git_time(); - - format!("{date} {time}") -} + const BUF: [u8; LEN] = concat(); + match str::from_utf8(&BUF) { + Ok(v) => v, + Err(_) => unreachable!(), + } +}; // Must be aligned to Lib/importlib/_bootstrap_external.py // Bumped to 2994 for new CommonConstant discriminants (BuiltinList, BuiltinSet) diff --git a/src/settings.rs b/src/settings.rs index 5233cf98d49..25398cd8fc4 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -199,7 +199,7 @@ fn help(parser: lexopt::Parser) -> ! { } fn version() -> ! { - println!("Python {}", rustpython_vm::version::get_version()); + println!("Python {}", rustpython_vm::version::RUSTPYTHON_VERSION); std::process::exit(0); }