Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ widestring = { workspace = true }
wasm-bindgen = { workspace = true, optional = true }

[build-dependencies]
chrono = { workspace = true }
glob = { workspace = true }
itertools = { workspace = true }

Expand Down
120 changes: 120 additions & 0 deletions crates/vm/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,17 +26,27 @@ 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={}",
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}");

Expand Down Expand Up @@ -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'"),
}
Comment on lines +199 to +206
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make invalid release levels fail with an actionable message.

Line 213 turns a typo in RUSTPYTHON_RELEASE_LEVEL into an opaque build-script panic. An explicit panic message here is much easier to diagnose.

Suggested fix
 fn release_to_n(release: &str) -> usize {
     match release {
         "alpha" => 0xA,
         "beta" => 0xB,
         "candidate" => 0xC,
         "final" => 0xD,
-        _ => unreachable!(),
+        _ => panic!("unsupported RUSTPYTHON_RELEASE_LEVEL: {release}"),
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn release_to_n(release: &str) -> usize {
match release {
"alpha" => 0xA,
"beta" => 0xB,
"candidate" => 0xC,
"final" => 0xD,
_ => unreachable!(),
}
fn release_to_n(release: &str) -> usize {
match release {
"alpha" => 0xA,
"beta" => 0xB,
"candidate" => 0xC,
"final" => 0xD,
_ => panic!("unsupported RUSTPYTHON_RELEASE_LEVEL: {release}"),
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/build.rs` around lines 207 - 214, The match arm in function
release_to_n currently uses unreachable!() for unknown release strings, causing
an opaque panic; update release_to_n to explicitly panic with a clear,
actionable message (including the received release value and that it came from
RUSTPYTHON_RELEASE_LEVEL) so typos/invalid values produce a descriptive error
instead of an opaque build-script panic.

}

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.
Expand Down
16 changes: 6 additions & 10 deletions crates/vm/src/stdlib/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
))
}
Expand Down Expand Up @@ -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 {
Expand Down
175 changes: 51 additions & 124 deletions crates/vm/src/version.rs
Original file line number Diff line number Diff line change
@@ -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<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)
Expand Down
Loading
Loading