diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index fb2a5c69c..14b572e08 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -28,12 +28,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Sync Sysconfig Targets + run: ${{ github.workspace }}/crates/uv-dev/sync_sysconfig_targets.sh + working-directory: ./crates/uv-dev + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Create Pull Request" uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: commit-message: "Sync latest Python releases" add-paths: | crates/uv-python/download-metadata.json + crates/uv-dev/src/generate_sysconfig_mappings.rs + crates/uv-python/src/sysconfig/generated_mappings.rs branch: "sync-python-releases" title: "Sync latest Python releases" body: "Automated update for Python releases." diff --git a/Cargo.lock b/Cargo.lock index 4aac23498..616d7b7a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3627,6 +3627,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4528,6 +4541,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "unscanny" version = "0.1.0" @@ -5049,10 +5068,12 @@ dependencies = [ "owo-colors", "poloto", "pretty_assertions", + "reqwest", "resvg", "schemars", "serde", "serde_json", + "serde_yaml", "tagu", "textwrap", "tokio", diff --git a/crates/uv-dev/Cargo.toml b/crates/uv-dev/Cargo.toml index 42215ed2c..c778d842b 100644 --- a/crates/uv-dev/Cargo.toml +++ b/crates/uv-dev/Cargo.toml @@ -44,10 +44,12 @@ markdown = { version = "1.0.0" } owo-colors = { workspace = true } poloto = { version = "19.1.2", optional = true } pretty_assertions = { version = "1.4.1" } +reqwest = { workspace = true } resvg = { version = "0.29.0", optional = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +serde_yaml = { version = "0.9.34" } tagu = { version = "0.1.6", optional = true } textwrap = { workspace = true } tokio = { workspace = true } diff --git a/crates/uv-dev/src/generate_all.rs b/crates/uv-dev/src/generate_all.rs index 1655f9a21..96f906830 100644 --- a/crates/uv-dev/src/generate_all.rs +++ b/crates/uv-dev/src/generate_all.rs @@ -4,7 +4,7 @@ use anyhow::Result; use crate::{ generate_cli_reference, generate_env_vars_reference, generate_json_schema, - generate_options_reference, + generate_options_reference, generate_sysconfig_mappings, }; #[derive(clap::Args)] @@ -26,10 +26,12 @@ pub(crate) enum Mode { DryRun, } -pub(crate) fn main(args: &Args) -> Result<()> { +pub(crate) async fn main(args: &Args) -> Result<()> { generate_json_schema::main(&generate_json_schema::Args { mode: args.mode })?; generate_options_reference::main(&generate_options_reference::Args { mode: args.mode })?; generate_cli_reference::main(&generate_cli_reference::Args { mode: args.mode })?; generate_env_vars_reference::main(&generate_env_vars_reference::Args { mode: args.mode })?; + generate_sysconfig_mappings::main(&generate_sysconfig_mappings::Args { mode: args.mode }) + .await?; Ok(()) } diff --git a/crates/uv-dev/src/generate_sysconfig_mappings.rs b/crates/uv-dev/src/generate_sysconfig_mappings.rs new file mode 100644 index 000000000..5599bf0be --- /dev/null +++ b/crates/uv-dev/src/generate_sysconfig_mappings.rs @@ -0,0 +1,198 @@ +//! 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/20250529/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, + host_cxx: Option, + target_cc: Option, + target_cxx: Option, +} + +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 { + println!("Downloading python-build-standalone cpython-unix/targets.yml ..."); + let body = reqwest::get(TARGETS_YML_URL).await?.text().await?; + + let parsed: BTreeMap = serde_yaml::from_str(&body)?; + + let mut replacements: BTreeMap<&str, BTreeMap> = 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 \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>> = 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 + } +} diff --git a/crates/uv-dev/src/lib.rs b/crates/uv-dev/src/lib.rs index 8ad97130a..c01cc62c4 100644 --- a/crates/uv-dev/src/lib.rs +++ b/crates/uv-dev/src/lib.rs @@ -11,6 +11,7 @@ use crate::generate_cli_reference::Args as GenerateCliReferenceArgs; use crate::generate_env_vars_reference::Args as GenerateEnvVarsReferenceArgs; use crate::generate_json_schema::Args as GenerateJsonSchemaArgs; use crate::generate_options_reference::Args as GenerateOptionsReferenceArgs; +use crate::generate_sysconfig_mappings::Args as GenerateSysconfigMetadataArgs; #[cfg(feature = "render")] use crate::render_benchmarks::RenderBenchmarksArgs; use crate::wheel_metadata::WheelMetadataArgs; @@ -22,6 +23,7 @@ mod generate_cli_reference; mod generate_env_vars_reference; mod generate_json_schema; mod generate_options_reference; +mod generate_sysconfig_mappings; mod render_benchmarks; mod wheel_metadata; @@ -45,6 +47,8 @@ enum Cli { GenerateCliReference(GenerateCliReferenceArgs), /// Generate the environment variables reference for the documentation. GenerateEnvVarsReference(GenerateEnvVarsReferenceArgs), + /// Generate the sysconfig metadata from derived targets. + GenerateSysconfigMetadata(GenerateSysconfigMetadataArgs), #[cfg(feature = "render")] /// Render the benchmarks. RenderBenchmarks(RenderBenchmarksArgs), @@ -57,11 +61,12 @@ pub async fn run() -> Result<()> { Cli::WheelMetadata(args) => wheel_metadata::wheel_metadata(args).await?, Cli::Compile(args) => compile::compile(args).await?, Cli::ClearCompile(args) => clear_compile::clear_compile(&args)?, - Cli::GenerateAll(args) => generate_all::main(&args)?, + Cli::GenerateAll(args) => generate_all::main(&args).await?, Cli::GenerateJSONSchema(args) => generate_json_schema::main(&args)?, Cli::GenerateOptionsReference(args) => generate_options_reference::main(&args)?, Cli::GenerateCliReference(args) => generate_cli_reference::main(&args)?, Cli::GenerateEnvVarsReference(args) => generate_env_vars_reference::main(&args)?, + Cli::GenerateSysconfigMetadata(args) => generate_sysconfig_mappings::main(&args).await?, #[cfg(feature = "render")] Cli::RenderBenchmarks(args) => render_benchmarks::render_benchmarks(&args)?, } diff --git a/crates/uv-dev/sync_sysconfig_targets.sh b/crates/uv-dev/sync_sysconfig_targets.sh new file mode 100755 index 000000000..f388fad21 --- /dev/null +++ b/crates/uv-dev/sync_sysconfig_targets.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Fetch latest python-build-standalone tag +latest_tag=$(curl -fsSL -H "Accept: application/json" https://github.com/astral-sh/python-build-standalone/releases/latest | jq -r .tag_name) + +# Validate we got a tag name back +if [[ -z "${latest_tag}" ]]; then + echo "Error: Failed to fetch the latest tag from astral-sh/python-build-standalone." >&2 + exit 1 +fi + +# Edit the sysconfig mapping endpoints +sed -i.bak "s|refs/tags/[^/]\+/cpython-unix|refs/tags/${latest_tag}/cpython-unix|g" src/generate_sysconfig_mappings.rs && rm -f src/generate_sysconfig_mappings.rs.bak +sed -i.bak "s|blob/[^/]\+/cpython-unix|blob/${latest_tag}/cpython-unix|g" src/generate_sysconfig_mappings.rs && rm -f src/generate_sysconfig_mappings.rs.bak + +# Regenerate mappings in case there's any changes +cargo dev generate-sysconfig-metadata diff --git a/crates/uv-python/src/sysconfig/generated_mappings.rs b/crates/uv-python/src/sysconfig/generated_mappings.rs new file mode 100644 index 000000000..1fac2348f --- /dev/null +++ b/crates/uv-python/src/sysconfig/generated_mappings.rs @@ -0,0 +1,100 @@ +//! DO NOT EDIT +//! +//! Generated with `cargo run dev generate-sysconfig-metadata` +//! Targets from +//! +#![allow(clippy::all)] +#![cfg_attr(any(), rustfmt::skip)] + +use std::collections::BTreeMap; +use std::sync::LazyLock; + +use crate::sysconfig::replacements::{ReplacementEntry, ReplacementMode}; + +/// Mapping for sysconfig keys to lookup and replace with the appropriate entry. +pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock>> = LazyLock::new(|| { + BTreeMap::from_iter([ + ("BLDSHARED".to_string(), vec![ + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mipsel-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/powerpc64le-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/riscv64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/s390x-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/x86_64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "clang".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() }, + ]), + ("CC".to_string(), vec![ + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mipsel-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/powerpc64le-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/riscv64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/s390x-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/x86_64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "clang".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() }, + ]), + ("CXX".to_string(), vec![ + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mipsel-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/powerpc64le-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/riscv64-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/s390x-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/x86_64-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "clang++".to_string() }, to: "c++".to_string() }, + ]), + ("LDCXXSHARED".to_string(), vec![ + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mipsel-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/powerpc64le-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/riscv64-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/s390x-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/x86_64-linux-gnu-g++".to_string() }, to: "c++".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "clang++".to_string() }, to: "c++".to_string() }, + ]), + ("LDSHARED".to_string(), vec![ + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mipsel-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/powerpc64le-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/riscv64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/s390x-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/x86_64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "clang".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() }, + ]), + ("LINKCC".to_string(), vec![ + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mipsel-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/powerpc64le-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/riscv64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/s390x-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/x86_64-linux-gnu-gcc".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "clang".to_string() }, to: "cc".to_string() }, + ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() }, + ]), + ("AR".to_string(), vec![ + ReplacementEntry { + mode: ReplacementMode::Full, + to: "ar".to_string(), + }, + ]), + ]) +}); diff --git a/crates/uv-python/src/sysconfig/mod.rs b/crates/uv-python/src/sysconfig/mod.rs index 822408024..bcf9d1471 100644 --- a/crates/uv-python/src/sysconfig/mod.rs +++ b/crates/uv-python/src/sysconfig/mod.rs @@ -25,131 +25,20 @@ //! ``` use std::borrow::Cow; -use std::collections::BTreeMap; use std::io::Write; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::LazyLock; use itertools::{Either, Itertools}; use tracing::trace; +use crate::sysconfig::generated_mappings::DEFAULT_VARIABLE_UPDATES; use crate::sysconfig::parser::{Error as ParseError, SysconfigData, Value}; mod cursor; +mod generated_mappings; mod parser; - -/// Replacement mode for sysconfig values. -#[derive(Debug)] -enum ReplacementMode { - Partial { from: String }, - Full, -} - -/// A replacement entry to patch in sysconfig data. -#[derive(Debug)] -struct ReplacementEntry { - mode: ReplacementMode, - to: String, -} - -impl ReplacementEntry { - /// Patches a sysconfig value either partially (replacing a specific word) or fully. - fn patch(&self, entry: &str) -> String { - match &self.mode { - ReplacementMode::Partial { from } => entry - .split_whitespace() - .map(|word| if word == from { &self.to } else { word }) - .collect::>() - .join(" "), - ReplacementMode::Full => self.to.clone(), - } - } -} - -/// Mapping for sysconfig keys to lookup and replace with the appropriate entry. -static DEFAULT_VARIABLE_UPDATES: LazyLock>> = - LazyLock::new(|| { - BTreeMap::from_iter([ - ( - "CC".to_string(), - vec![ - ReplacementEntry { - mode: ReplacementMode::Partial { - from: "clang".to_string(), - }, - to: "cc".to_string(), - }, - ReplacementEntry { - mode: ReplacementMode::Partial { - from: "/usr/bin/aarch64-linux-gnu-gcc".to_string(), - }, - to: "cc".to_string(), - }, - ], - ), - ( - "CXX".to_string(), - vec![ - ReplacementEntry { - mode: ReplacementMode::Partial { - from: "clang++".to_string(), - }, - to: "c++".to_string(), - }, - ReplacementEntry { - mode: ReplacementMode::Partial { - from: "/usr/bin/x86_64-linux-gnu-g++".to_string(), - }, - to: "c++".to_string(), - }, - ], - ), - ( - "BLDSHARED".to_string(), - vec![ReplacementEntry { - mode: ReplacementMode::Partial { - from: "clang".to_string(), - }, - to: "cc".to_string(), - }], - ), - ( - "LDSHARED".to_string(), - vec![ReplacementEntry { - mode: ReplacementMode::Partial { - from: "clang".to_string(), - }, - to: "cc".to_string(), - }], - ), - ( - "LDCXXSHARED".to_string(), - vec![ReplacementEntry { - mode: ReplacementMode::Partial { - from: "clang++".to_string(), - }, - to: "c++".to_string(), - }], - ), - ( - "LINKCC".to_string(), - vec![ReplacementEntry { - mode: ReplacementMode::Partial { - from: "clang".to_string(), - }, - to: "cc".to_string(), - }], - ), - ( - "AR".to_string(), - vec![ReplacementEntry { - mode: ReplacementMode::Full, - to: "ar".to_string(), - }], - ), - ]) - }); +mod replacements; /// Update the `sysconfig` data in a Python installation. pub(crate) fn update_sysconfig( diff --git a/crates/uv-python/src/sysconfig/replacements.rs b/crates/uv-python/src/sysconfig/replacements.rs new file mode 100644 index 000000000..63119bcd8 --- /dev/null +++ b/crates/uv-python/src/sysconfig/replacements.rs @@ -0,0 +1,27 @@ +/// Replacement mode for sysconfig values. +#[derive(Debug)] +pub(crate) enum ReplacementMode { + Partial { from: String }, + Full, +} + +/// A replacement entry to patch in sysconfig data. +#[derive(Debug)] +pub(crate) struct ReplacementEntry { + pub(crate) mode: ReplacementMode, + pub(crate) to: String, +} + +impl ReplacementEntry { + /// Patches a sysconfig value either partially (replacing a specific word) or fully. + pub(crate) fn patch(&self, entry: &str) -> String { + match &self.mode { + ReplacementMode::Partial { from } => entry + .split_whitespace() + .map(|word| if word == from { &self.to } else { word }) + .collect::>() + .join(" "), + ReplacementMode::Full => self.to.clone(), + } + } +}