feat: add dynamically generated sysconfig replacement mappings (#13441)

## Summary

Implementation referenced in
https://github.com/astral-sh/uv/pull/12239#issuecomment-2744880003

Closes #12919 #12901

This makes the sysconfig replacements mappings dynamically generated
from
https://github.com/astral-sh/python-build-standalone/blob/main/cpython-unix/targets.yml

## Test Plan

cargo dev tests, and tested scenario from
https://github.com/astral-sh/uv/issues/12901#issuecomment-2822107454
This commit is contained in:
samypr100 2025-06-02 11:58:30 -04:00 committed by GitHub
parent 73eb2dfb1f
commit d65c146b21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 387 additions and 117 deletions

View file

@ -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."

21
Cargo.lock generated
View file

@ -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",

View file

@ -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 }

View file

@ -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(())
}

View file

@ -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<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/20250529/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
}
}

View file

@ -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)?,
}

View file

@ -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

View file

@ -0,0 +1,100 @@
//! DO NOT EDIT
//!
//! Generated with `cargo run dev generate-sysconfig-metadata`
//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20250529/cpython-unix/targets.yml>
//!
#![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<BTreeMap<String, Vec<ReplacementEntry>>> = 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(),
},
]),
])
});

View file

@ -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::<Vec<_>>()
.join(" "),
ReplacementMode::Full => self.to.clone(),
}
}
}
/// Mapping for sysconfig keys to lookup and replace with the appropriate entry.
static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<ReplacementEntry>>> =
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(

View file

@ -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::<Vec<_>>()
.join(" "),
ReplacementMode::Full => self.to.clone(),
}
}
}