Const eval all of version.rs (#7923)

`version.rs` essentially consists of constants that can be baked in at
compile time. I moved most of `version.rs` to `build.rs`. The constants
are passed via rustc's environment then stored in the binary.
This commit is contained in:
Joshua Megnauth authored and GitHub committed 2026-05-19 21:02:56 +09:00
1 parent 1bb42adc73
commit d1eec5d168
5 files changed
+178 -134

No files matched your search

+1
View File
@@ -99,6 +99,7 @@ widestring = { workspace = true }
wasm-bindgen = { workspace = true, optional = true }
[build-dependencies]
chrono = { workspace = true }
glob = { workspace = true }
itertools = { workspace = true }
+120
View File
@@ -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<String> {
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<Local> {
let timestamp = git_timestamp().parse::<u64>().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<std::ffi::OsStr>, args: &[&str]) -> io::Result<String> {
Command::new(&cmd).args(args).output().and_then(|output| {
// TODO: Switch to exit_ok()? when stable.
+6 -10
View File
@@ -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 {
+50 -123
View File
@@ -1,145 +1,72 @@
//! Several function to retrieve version information.
//! 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`.
use chrono::{Local, prelude::DateTime};
use core::time::Duration;
use std::time::UNIX_EPOCH;
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(),
};
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;
format!(
"{:.80} ({:.80}) \n[RustPython {} with {:.80}{}]", // \n is PyPy convention
get_version_number(),
get_build_info(),
env!("CARGO_PKG_VERSION"),
COMPILER,
msc_info,
)
}
const fn concat() -> [u8; LEN] {
let mut bytes_temp = [0u8; LEN];
#[must_use]
pub fn get_version_number() -> String {
format!("{MAJOR}.{MINOR}.{MICRO}{RELEASELEVEL}")
}
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 fn get_winver_number() -> String {
format!("{MAJOR}.{MINOR}")
}
const COMPILER: &str = env!("RUSTC_VERSION");
#[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();
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(),
)
}
#[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<Local> {
let timestamp = option_env!("RUSTPYTHON_GIT_TIMESTAMP").unwrap_or_default();
let timestamp = timestamp.parse::<u64>().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)
+1 -1
View File
@@ -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);
}