Skip to content

Commit c5143aa

Browse files
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.
1 parent 0a2461a commit c5143aa

5 files changed

Lines changed: 179 additions & 135 deletions

File tree

crates/vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ widestring = { workspace = true }
9999
wasm-bindgen = { workspace = true, optional = true }
100100

101101
[build-dependencies]
102+
chrono = { workspace = true }
102103
glob = { workspace = true }
103104
itertools = { workspace = true }
104105

crates/vm/build.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
reason = "build scripts cannot use rustpython-host_env"
44
)]
55

6+
use chrono::{Local, prelude::DateTime};
7+
use core::time::Duration;
68
use itertools::Itertools;
79
use std::{
810
env,
@@ -24,17 +26,27 @@ fn main() {
2426
}
2527
println!("cargo:rerun-if-changed=../../Lib/importlib/_bootstrap.py");
2628

29+
// = 3.14.0alpha
30+
python_version(3, 14, 0, "alpha", 0);
31+
2732
println!("cargo:rustc-env=RUSTPYTHON_GIT_HASH={}", git_hash());
2833
println!(
2934
"cargo:rustc-env=RUSTPYTHON_GIT_TIMESTAMP={}",
3035
git_timestamp()
3136
);
3237
println!("cargo:rustc-env=RUSTPYTHON_GIT_TAG={}", git_tag());
3338
println!("cargo:rustc-env=RUSTPYTHON_GIT_BRANCH={}", git_branch());
39+
println!(
40+
"cargo:rustc-env=RUSTPYTHON_GIT_IDENTIFIER={}",
41+
git_identifier()
42+
);
43+
println!("cargo:rustc-env=RUSTPYTHON_BUILD_INFO={}", get_build_info());
3444
println!("cargo:rustc-env=RUSTC_VERSION={}", rustc_version());
3545

3646
let release_level = option_env!("RUSTPYTHON_RELEASE_LEVEL").unwrap_or("alpha");
3747
println!("cargo:rustc-env=RUSTPYTHON_RELEASE_LEVEL={release_level}");
48+
let release_level_n = release_to_n(release_level);
49+
println!("cargo:rustc-env=RUSTPYTHON_RELEASE_LEVEL_N={release_level_n}");
3850
let release_serial = option_env!("RUSTPYTHON_RELEASE_SERIAL").unwrap_or("0");
3951
println!("cargo:rustc-env=RUSTPYTHON_RELEASE_SERIAL={release_serial}");
4052

@@ -81,11 +93,119 @@ fn git(args: &[&str]) -> io::Result<String> {
8193
command("git", args)
8294
}
8395

96+
#[must_use]
97+
fn get_build_info() -> String {
98+
// See: https://reproducible-builds.org/docs/timestamps/
99+
let revision = git_hash();
100+
let separator = if revision.is_empty() { "" } else { ":" };
101+
let identifier = git_identifier();
102+
103+
format!(
104+
"{id}{sep}{revision}, {date:.20}, {time:.9}",
105+
id = if identifier.is_empty() {
106+
"default"
107+
} else {
108+
&identifier
109+
},
110+
sep = separator,
111+
revision = revision,
112+
date = get_git_date(),
113+
time = get_git_time(),
114+
)
115+
}
116+
117+
fn git_identifier() -> String {
118+
let tag = git_tag();
119+
if tag.is_empty() || tag.eq_ignore_ascii_case("undefined") {
120+
git_branch()
121+
} else {
122+
tag
123+
}
124+
}
125+
126+
fn get_git_timestamp_datetime() -> DateTime<Local> {
127+
let timestamp = git_timestamp().parse::<u64>().unwrap_or_default();
128+
let datetime = UNIX_EPOCH + Duration::from_secs(timestamp);
129+
datetime.into()
130+
}
131+
132+
#[must_use]
133+
fn get_git_date() -> String {
134+
let datetime = get_git_timestamp_datetime();
135+
136+
datetime.format("%b %e %Y").to_string()
137+
}
138+
139+
#[must_use]
140+
fn get_git_time() -> String {
141+
let datetime = get_git_timestamp_datetime();
142+
143+
datetime.format("%H:%M:%S").to_string()
144+
}
145+
84146
fn rustc_version() -> String {
85147
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
86148
command(rustc, &["-V"]).unwrap_or_else(|_| "rustc [unknown]".into())
87149
}
88150

151+
fn python_version(major: usize, minor: usize, micro: usize, release: &str, serial: usize) {
152+
println!("cargo:rustc-env=MAJOR_CPY={major}");
153+
println!("cargo:rustc-env=MINOR_CPY={minor}");
154+
println!("cargo:rustc-env=MICRO_CPY={micro}");
155+
println!("cargo:rustc-env=RELEASE_LEVEL_CPY={release}");
156+
println!(
157+
"cargo:rustc-env=RELEASE_LEVEL_N_CPY={}",
158+
release_to_n(release)
159+
);
160+
println!("cargo:rustc-env=SERIAL_CPY={serial}");
161+
162+
println!("cargo:rustc-env=WINVER_CPY={major}.{minor}",);
163+
164+
let cpy_version = format!("{major}.{minor}.{micro}.{release}");
165+
166+
let (left, right) = get_version(&cpy_version);
167+
println!("cargo:rustc-env=RUSTPYTHON_VERSION_LEFT={left}");
168+
println!("cargo:rustc-env=RUSTPYTHON_VERSION_RIGHT={right}");
169+
}
170+
171+
#[must_use]
172+
fn get_version(cpy_version: &str) -> (String, String) {
173+
// Windows: include MSC v. for compatibility with ctypes.util.find_library
174+
// MSC v.1929 = VS 2019, version 14+ makes find_msvcrt() return None
175+
let msc_info = cfg_select! {
176+
windows => {{
177+
// Include both RustPython identifier and MSC v. for compatibility
178+
if cfg!(target_pointer_width = "64") {
179+
" MSC v.1929 64 bit (AMD64)"
180+
} else {
181+
" MSC v.1929 32 bit (Intel)"
182+
}
183+
}},
184+
_ => "",
185+
};
186+
187+
// `left` and `right` are split by \n like PyPy. Passing a string with a newline to rustc
188+
// truncates everything from the newline onward, so we have to manually combine them later.
189+
let left = format!("{:.80} ({:.80})", cpy_version, get_build_info());
190+
let right = format!(
191+
"[RustPython {} with {:.80}{}]",
192+
env!("CARGO_PKG_VERSION"),
193+
rustc_version(),
194+
msc_info,
195+
);
196+
(left, right)
197+
}
198+
199+
fn release_to_n(release: &str) -> usize {
200+
match release {
201+
"alpha" => 0xA,
202+
"beta" => 0xB,
203+
"candidate" => 0xC,
204+
"final" => 0xD,
205+
_ => panic!("`release` must be one of: 'alpha', 'beta', 'candidate', 'final'"),
206+
}
207+
}
208+
89209
fn command(cmd: impl AsRef<std::ffi::OsStr>, args: &[&str]) -> io::Result<String> {
90210
Command::new(&cmd).args(args).output().and_then(|output| {
91211
// TODO: Switch to exit_ok()? when stable.

crates/vm/src/stdlib/sys.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ pub mod sys {
649649
fn _git(vm: &VirtualMachine) -> PyTupleRef {
650650
vm.new_tuple((
651651
ascii!("RustPython"),
652-
version::get_git_identifier(),
652+
version::GIT_IDENTIFIER,
653653
version::GIT_REVISION,
654654
))
655655
}
@@ -706,17 +706,13 @@ pub mod sys {
706706
vm.ctx.none()
707707
}
708708

709-
#[pyattr]
710-
fn version(_vm: &VirtualMachine) -> String {
711-
version::get_version()
712-
}
709+
#[pyattr(name = "version")]
710+
const VERSION: &str = version::RUSTPYTHON_VERSION;
713711

712+
// Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility
714713
#[cfg(windows)]
715-
#[pyattr]
716-
fn winver(_vm: &VirtualMachine) -> String {
717-
// Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility
718-
version::get_winver_number()
719-
}
714+
#[pyattr(name = "winver")]
715+
const WINVER: &str = version::WINVER;
720716

721717
#[pyattr]
722718
fn _xoptions(vm: &VirtualMachine) -> PyDictRef {

crates/vm/src/version.rs

Lines changed: 51 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,72 @@
1-
//! Several function to retrieve version information.
2-
3-
use chrono::{Local, prelude::DateTime};
4-
use core::time::Duration;
5-
use std::time::UNIX_EPOCH;
1+
//! Version info constants.
2+
//!
3+
//! Most of the constants are auto calculated at compile time. The main exception is the
4+
//! target CPython version. This is defined and updated in `build.rs`.
5+
6+
macro_rules! parse_consts {
7+
($name: ident, $var: literal) => {
8+
pub const $name: usize = match usize::from_str_radix(env!($var), 10) {
9+
Ok(v) => v,
10+
Err(_) => panic!(concat!("Compile with Cargo to get '", $var, "'")),
11+
};
12+
};
13+
}
614

7-
// = 3.14.0alpha
8-
pub const MAJOR: usize = 3;
9-
pub const MINOR: usize = 14;
10-
pub const MICRO: usize = 0;
11-
pub const RELEASELEVEL: &str = "alpha";
12-
pub const RELEASELEVEL_N: usize = 0xA;
13-
pub const SERIAL: usize = 0;
15+
// CPython target version info
16+
parse_consts!(MAJOR, "MAJOR_CPY");
17+
parse_consts!(MINOR, "MINOR_CPY");
18+
parse_consts!(MICRO, "MICRO_CPY");
19+
pub const RELEASELEVEL: &str = env!("RELEASE_LEVEL_CPY");
20+
parse_consts!(RELEASELEVEL_N, "RELEASE_LEVEL_N_CPY");
21+
parse_consts!(SERIAL, "SERIAL_CPY");
1422
pub const VERSION_HEX: usize =
1523
(MAJOR << 24) | (MINOR << 16) | (MICRO << 8) | (RELEASELEVEL_N << 4) | SERIAL;
1624

25+
#[cfg(windows)]
26+
pub const WINVER: &str = env!("WINVER_CPY");
27+
1728
pub const GIT_REVISION: &str = env!("RUSTPYTHON_GIT_HASH");
18-
const GIT_TAG: &str = env!("RUSTPYTHON_GIT_TAG");
19-
const GIT_BRANCH: &str = env!("RUSTPYTHON_GIT_BRANCH");
29+
pub const GIT_IDENTIFIER: &str = env!("RUSTPYTHON_GIT_IDENTIFIER");
30+
// const GIT_TAG: &str = env!("RUSTPYTHON_GIT_TAG");
31+
// const GIT_BRANCH: &str = env!("RUSTPYTHON_GIT_BRANCH");
2032

2133
// RustPython version
22-
pub const MAJOR_IMPL: usize = match usize::from_str_radix(env!("CARGO_PKG_VERSION_MAJOR"), 10) {
23-
Ok(v) => v,
24-
Err(_) => panic!("Compile with Cargo to get 'CARGO_PKG_VERSION_MAJOR'"),
25-
};
26-
pub const MINOR_IMPL: usize = match usize::from_str_radix(env!("CARGO_PKG_VERSION_MINOR"), 10) {
27-
Ok(v) => v,
28-
Err(_) => panic!("Compile with Cargo to get 'CARGO_PKG_VERSION_MINOR'"),
29-
};
30-
pub const MICRO_IMPL: usize = match usize::from_str_radix(env!("CARGO_PKG_VERSION_PATCH"), 10) {
31-
Ok(v) => v,
32-
Err(_) => panic!("Compile with Cargo to get 'CARGO_PKG_VERSION_PATCH'"),
33-
};
34+
parse_consts!(MAJOR_IMPL, "CARGO_PKG_VERSION_MAJOR");
35+
parse_consts!(MINOR_IMPL, "CARGO_PKG_VERSION_MINOR");
36+
parse_consts!(MICRO_IMPL, "CARGO_PKG_VERSION_PATCH");
3437
pub const RELEASELEVEL_IMPL: &str = env!("RUSTPYTHON_RELEASE_LEVEL");
35-
pub const SERIAL_IMPL: usize = match usize::from_str_radix(env!("RUSTPYTHON_RELEASE_SERIAL"), 10) {
36-
Ok(v) => v,
37-
Err(_) => panic!("Compile with Cargo to get 'RUSTPYTHON_RELEASE_SERIAL'"),
38-
};
38+
parse_consts!(RELEASELEVEL_N_IMPL, "RUSTPYTHON_RELEASE_LEVEL_N");
39+
parse_consts!(SERIAL_IMPL, "RUSTPYTHON_RELEASE_SERIAL");
3940
pub const VERSION_HEX_IMPL: usize = (MAJOR_IMPL << 24)
4041
| (MINOR_IMPL << 16)
4142
| (MICRO_IMPL << 8)
42-
| (RELEASELEVEL_N << 4)
43+
| (RELEASELEVEL_N_IMPL << 4)
4344
| SERIAL_IMPL;
4445

45-
#[must_use]
46-
pub fn get_version() -> String {
47-
// Windows: include MSC v. for compatibility with ctypes.util.find_library
48-
// MSC v.1929 = VS 2019, version 14+ makes find_msvcrt() return None
49-
let msc_info = cfg_select! {
50-
windows => {{
51-
let arch = if cfg!(target_pointer_width = "64") {
52-
"64 bit (AMD64)"
53-
} else {
54-
"32 bit (Intel)"
55-
};
56-
// Include both RustPython identifier and MSC v. for compatibility
57-
format!(" MSC v.1929 {arch}",)
58-
}},
59-
_ => String::new(),
60-
};
61-
62-
format!(
63-
"{:.80} ({:.80}) \n[RustPython {} with {:.80}{}]", // \n is PyPy convention
64-
get_version_number(),
65-
get_build_info(),
66-
env!("CARGO_PKG_VERSION"),
67-
COMPILER,
68-
msc_info,
69-
)
70-
}
71-
72-
#[must_use]
73-
pub fn get_version_number() -> String {
74-
format!("{MAJOR}.{MINOR}.{MICRO}{RELEASELEVEL}")
75-
}
76-
77-
#[must_use]
78-
pub fn get_winver_number() -> String {
79-
format!("{MAJOR}.{MINOR}")
80-
}
81-
82-
const COMPILER: &str = env!("RUSTC_VERSION");
46+
pub const RUSTPYTHON_BUILD_INFO: &str = env!("RUSTPYTHON_BUILD_INFO");
47+
pub const RUSTPYTHON_VERSION: &str = const {
48+
const LEFT: &str = env!("RUSTPYTHON_VERSION_LEFT");
49+
const RIGHT: &str = env!("RUSTPYTHON_VERSION_RIGHT");
50+
const LEN: usize = LEFT.len() + RIGHT.len() + 1;
8351

84-
#[must_use]
85-
pub fn get_build_info() -> String {
86-
// See: https://reproducible-builds.org/docs/timestamps/
87-
let separator = if GIT_REVISION.is_empty() { "" } else { ":" };
88-
let git_identifier = get_git_identifier();
52+
const fn concat() -> [u8; LEN] {
53+
let mut bytes_temp = [0u8; LEN];
8954

90-
format!(
91-
"{id}{sep}{revision}, {date:.20}, {time:.9}",
92-
id = if git_identifier.is_empty() {
93-
"default"
94-
} else {
95-
git_identifier
96-
},
97-
sep = separator,
98-
revision = GIT_REVISION,
99-
date = get_git_date(),
100-
time = get_git_time(),
101-
)
102-
}
55+
let (left, _) = bytes_temp.split_at_mut(LEFT.len());
56+
left.copy_from_slice(LEFT.as_bytes());
57+
let (_, right) = bytes_temp.split_at_mut(LEFT.len() + 1);
58+
right.copy_from_slice(RIGHT.as_bytes());
59+
bytes_temp[LEFT.len()] = b'\n';
10360

104-
#[must_use]
105-
pub const fn get_git_identifier() -> &'static str {
106-
if GIT_TAG.is_empty() || GIT_TAG.eq_ignore_ascii_case("undefined") {
107-
GIT_BRANCH
108-
} else {
109-
GIT_TAG
61+
bytes_temp
11062
}
111-
}
112-
113-
fn get_git_timestamp_datetime() -> DateTime<Local> {
114-
let timestamp = option_env!("RUSTPYTHON_GIT_TIMESTAMP").unwrap_or_default();
115-
let timestamp = timestamp.parse::<u64>().unwrap_or_default();
116-
117-
let datetime = UNIX_EPOCH + Duration::from_secs(timestamp);
118-
119-
datetime.into()
120-
}
121-
122-
#[must_use]
123-
pub fn get_git_date() -> String {
124-
let datetime = get_git_timestamp_datetime();
125-
126-
datetime.format("%b %e %Y").to_string()
127-
}
128-
129-
#[must_use]
130-
pub fn get_git_time() -> String {
131-
let datetime = get_git_timestamp_datetime();
132-
133-
datetime.format("%H:%M:%S").to_string()
134-
}
13563

136-
#[must_use]
137-
pub fn get_git_datetime() -> String {
138-
let date = get_git_date();
139-
let time = get_git_time();
140-
141-
format!("{date} {time}")
142-
}
64+
const BUF: [u8; LEN] = concat();
65+
match str::from_utf8(&BUF) {
66+
Ok(v) => v,
67+
Err(_) => unreachable!(),
68+
}
69+
};
14370

14471
// Must be aligned to Lib/importlib/_bootstrap_external.py
14572
// Bumped to 2994 for new CommonConstant discriminants (BuiltinList, BuiltinSet)

0 commit comments

Comments
 (0)