-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathgenerate_sysconfig_mappings.rs
More file actions
203 lines (176 loc) · 6.97 KB
/
generate_sysconfig_mappings.rs
File metadata and controls
203 lines (176 loc) · 6.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! Generate sysconfig mappings for supported python-build-standalone *nix platforms.
use anstream::println;
use anyhow::{Result, bail};
use pretty_assertions::StrComparison;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::fmt::Write;
use std::path::PathBuf;
use crate::ROOT_DIR;
use crate::generate_all::Mode;
/// Contains current supported targets
const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20260310/cpython-unix/targets.yml";
#[derive(clap::Args)]
pub(crate) struct Args {
#[arg(long, default_value_t, value_enum)]
pub(crate) mode: Mode,
}
#[derive(Debug, Deserialize)]
struct TargetConfig {
host_cc: Option<String>,
host_cxx: Option<String>,
target_cc: Option<String>,
target_cxx: Option<String>,
}
pub(crate) async fn main(args: &Args) -> Result<()> {
let reference_string = generate().await?;
let filename = "generated_mappings.rs";
let reference_path = PathBuf::from(ROOT_DIR)
.join("crates")
.join("uv-python")
.join("src")
.join("sysconfig")
.join(filename);
match args.mode {
Mode::DryRun => {
println!("{reference_string}");
}
Mode::Check => match fs_err::read_to_string(reference_path) {
Ok(current) => {
if current == reference_string {
println!("Up-to-date: {filename}");
} else {
let comparison = StrComparison::new(¤t, &reference_string);
bail!(
"{filename} changed, please run `cargo dev generate-sysconfig-metadata`:\n{comparison}"
);
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
bail!("{filename} not found, please run `cargo dev generate-sysconfig-metadata`");
}
Err(err) => {
bail!(
"{filename} changed, please run `cargo dev generate-sysconfig-metadata`:\n{err}"
);
}
},
Mode::Write => match fs_err::read_to_string(&reference_path) {
Ok(current) => {
if current == reference_string {
println!("Up-to-date: {filename}");
} else {
println!("Updating: {filename}");
fs_err::write(reference_path, reference_string.as_bytes())?;
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
println!("Updating: {filename}");
fs_err::write(reference_path, reference_string.as_bytes())?;
}
Err(err) => {
bail!(
"{filename} changed, please run `cargo dev generate-sysconfig-metadata`:\n{err}"
);
}
},
}
Ok(())
}
async fn generate() -> Result<String> {
println!("Downloading python-build-standalone cpython-unix/targets.yml ...");
let body = reqwest::get(TARGETS_YML_URL).await?.text().await?;
let parsed: BTreeMap<String, TargetConfig> = serde_yaml::from_str(&body)?;
let mut replacements: BTreeMap<&str, BTreeMap<String, String>> = BTreeMap::new();
for targets_config in parsed.values() {
for sysconfig_cc_entry in ["CC", "LDSHARED", "BLDSHARED", "LINKCC"] {
if let Some(ref from_cc) = targets_config.host_cc {
replacements
.entry(sysconfig_cc_entry)
.or_default()
.insert(from_cc.to_owned(), "cc".to_string());
}
if let Some(ref from_cc) = targets_config.target_cc {
replacements
.entry(sysconfig_cc_entry)
.or_default()
.insert(from_cc.to_owned(), "cc".to_string());
}
}
for sysconfig_cxx_entry in ["CXX", "LDCXXSHARED"] {
if let Some(ref from_cxx) = targets_config.host_cxx {
replacements
.entry(sysconfig_cxx_entry)
.or_default()
.insert(from_cxx.to_owned(), "c++".to_string());
}
if let Some(ref from_cxx) = targets_config.target_cxx {
replacements
.entry(sysconfig_cxx_entry)
.or_default()
.insert(from_cxx.to_owned(), "c++".to_string());
}
}
}
let mut output = String::new();
// Opening statements
output.push_str("//! DO NOT EDIT\n");
output.push_str("//!\n");
output.push_str("//! Generated with `cargo run dev generate-sysconfig-metadata`\n");
output.push_str("//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20260310/cpython-unix/targets.yml>\n");
output.push_str("//!\n");
// Disable clippy/fmt
output.push_str("#![allow(clippy::all)]\n");
output.push_str("#![cfg_attr(any(), rustfmt::skip)]\n\n");
// Begin main code
output.push_str("use std::collections::BTreeMap;\n");
output.push_str("use std::sync::LazyLock;\n\n");
output.push_str("use crate::sysconfig::replacements::{ReplacementEntry, ReplacementMode};\n\n");
output.push_str(
"/// Mapping for sysconfig keys to lookup and replace with the appropriate entry.\n",
);
output.push_str("pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<ReplacementEntry>>> = LazyLock::new(|| {\n");
output.push_str(" BTreeMap::from_iter([\n");
// Add Replacement Entries for CC, CXX, etc.
for (key, entries) in &replacements {
writeln!(output, " (\"{key}\".to_string(), vec![")?;
for (from, to) in entries {
writeln!(
output,
" ReplacementEntry {{ mode: ReplacementMode::Partial {{ from: \"{from}\".to_string() }}, to: \"{to}\".to_string() }},"
)?;
}
writeln!(output, " ]),")?;
}
// Add AR case last
output.push_str(" (\"AR\".to_string(), vec![\n");
output.push_str(" ReplacementEntry {\n");
output.push_str(" mode: ReplacementMode::Full,\n");
output.push_str(" to: \"ar\".to_string(),\n");
output.push_str(" },\n");
output.push_str(" ]),\n");
// Closing
output.push_str(" ])\n});\n");
Ok(output)
}
#[cfg(test)]
mod tests {
use std::env;
use anyhow::Result;
use uv_static::EnvVars;
use crate::generate_all::Mode;
use super::{Args, main};
#[tokio::test]
async fn test_generate_sysconfig_mappings() -> Result<()> {
// Skip this test in CI to avoid redundancy with the dedicated CI job
if env::var_os(EnvVars::CI).is_some() {
return Ok(());
}
let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") {
Mode::Write
} else {
Mode::Check
};
main(&Args { mode }).await
}
}