uv/crates/uv-dev/src/generate_sysconfig_mappings.rs
github-actions[bot] d9c34259b3
Sync latest Python releases (#13996)
Automated update for Python releases.

---------

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-06-12 09:47:28 -05:00

198 lines
6.8 KiB
Rust

//! 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/20250612/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(&current, &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_string(), "cc".to_string());
}
if let Some(ref from_cc) = targets_config.target_cc {
replacements
.entry(sysconfig_cc_entry)
.or_default()
.insert(from_cc.to_string(), "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_string(), "c++".to_string());
}
if let Some(ref from_cxx) = targets_config.target_cxx {
replacements
.entry(sysconfig_cxx_entry)
.or_default()
.insert(from_cxx.to_string(), "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/20250612/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<()> {
let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") {
Mode::Write
} else {
Mode::Check
};
main(&Args { mode }).await
}
}