Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1362,17 +1362,28 @@ def _check_raw_dylib_macro(session: nox.Session):
min_minor = int(min_version.split(".")[1])
max_minor = int(max_version.split(".")[1])

# Build the set of DLL names that default_lib_name_windows can produce
expected_dlls = {"python3", "python3_d"}
# Build the set of raw-dylib link names that default_lib_name_windows can produce
expected_dlls = {
"python3",
"python3_d",
"libpython3",
"libpython3_d",
}
for minor in range(min_minor, max_minor + 1):
expected_dlls.add(f"python3{minor}")
expected_dlls.add(f"python3{minor}_d")
expected_dlls.add(f"libpython3.{minor}")
expected_dlls.add(f"libpython3.{minor}_d")
if minor >= 13:
expected_dlls.add(f"python3{minor}t")
expected_dlls.add(f"python3{minor}t_d")
expected_dlls.add(f"libpython3.{minor}t")
expected_dlls.add(f"libpython3.{minor}t_d")
if minor >= 15:
expected_dlls.add("python3t")
expected_dlls.add("python3t_d")
expected_dlls.add("libpython3t")
expected_dlls.add("libpython3t_d")

# PyPy DLL names (libpypy3.X-c.dll)
pypy_min, pypy_max = _parse_supported_interpreter_version("pypy")
Expand All @@ -1383,7 +1394,7 @@ def _check_raw_dylib_macro(session: nox.Session):

# Parse the DLL name list in the extern_libpython!(@impl ...) invocation
lib_rs = (PYO3_DIR / "pyo3-ffi" / "src" / "impl_" / "macros.rs").read_text()
found_dlls = set(re.findall(r'"((?:python|libpypy)[^"]+)"', lib_rs))
found_dlls = set(re.findall(r'"((?:python|libpython|libpypy)[^"]+)"', lib_rs))

missing = expected_dlls - found_dlls
extra = found_dlls - expected_dlls
Expand Down
72 changes: 58 additions & 14 deletions pyo3-build-config/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2302,7 +2302,12 @@ const WINDOWS_STABLE_ABI_DEBUG_LIB_NAME: &str = "python3_d";
#[allow(dead_code)]
fn default_lib_name_for_target(abi: PythonAbi, target: &Triple) -> String {
if target.operating_system == OperatingSystem::Windows {
default_lib_name_windows(abi, false, false).unwrap()
default_lib_name_windows(
abi,
matches!(target.environment, Environment::Gnu | Environment::GnuLlvm),
false,
)
.unwrap()
} else {
default_lib_name_unix(
abi,
Expand All @@ -2314,6 +2319,9 @@ fn default_lib_name_for_target(abi: PythonAbi, target: &Triple) -> String {
}

fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result<String> {
// MSYS2 MinGW-style Windows targets ship libpython with a `lib` prefix.
let lib_prefix = if mingw { "lib" } else { "" };

if abi.implementation.is_pypy() {
// PyPy on Windows ships `libpypy3.X-c.dll` (e.g. `libpypy3.11-c.dll`),
// not CPython's `pythonXY.dll`. With raw-dylib linking we need the real
Expand All @@ -2326,8 +2334,8 @@ fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result<
// CPython bug: linking against python3_d.dll raises error
// https://github.com/python/cpython/issues/101614
Ok(format!(
"python{}{}_d",
abi.version.major, abi.version.minor
"{}python{}{}_d",
lib_prefix, abi.version.major, abi.version.minor
))
} else if abi.kind == PythonAbiKind::Stable(StableAbi::Abi3)
|| abi.kind == PythonAbiKind::Stable(StableAbi::Abi3t)
Expand All @@ -2340,34 +2348,43 @@ fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result<
if abi.kind == PythonAbiKind::Stable(StableAbi::Abi3t) {
lib_name = lib_name.replace("python3", "python3t");
}
Ok(lib_name)
Ok(format!("{}{}", lib_prefix, lib_name))
} else if mingw {
ensure!(
!abi.kind.is_free_threaded(),
"MinGW free-threaded builds are not currently tested or supported"
);
// https://packages.msys2.org/base/mingw-w64-python
Ok(format!("python{}.{}", abi.version.major, abi.version.minor))
Ok(format!(
"{}python{}.{}",
lib_prefix, abi.version.major, abi.version.minor
))
} else if abi.kind().is_free_threaded() {
#[expect(deprecated, reason = "using constant internally")]
{
ensure!(abi.version() >= PythonVersion::PY313, "Cannot compile extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", abi.version.major, abi.version.minor);
}
if debug {
Ok(format!(
"python{}{}t_d",
abi.version.major, abi.version.minor
"{}python{}{}t_d",
lib_prefix, abi.version.major, abi.version.minor
))
} else {
Ok(format!("python{}{}t", abi.version.major, abi.version.minor))
Ok(format!(
"{}python{}{}t",
lib_prefix, abi.version.major, abi.version.minor
))
}
} else if debug {
Ok(format!(
"python{}{}_d",
abi.version.major, abi.version.minor
"{}python{}{}_d",
lib_prefix, abi.version.major, abi.version.minor
))
} else {
Ok(format!("python{}{}", abi.version.major, abi.version.minor))
Ok(format!(
"{}python{}{}",
lib_prefix, abi.version.major, abi.version.minor
))
}
}

Expand Down Expand Up @@ -3091,7 +3108,7 @@ mod tests {
let implementation = PythonImplementation::CPython;
let version = PythonVersion::PY39;
let config = InterpreterConfigBuilder::new(implementation, version)
.lib_name("python39".to_string())
.lib_name("libpython3.9".to_string())
.lib_dir("/usr/lib/mingw".to_string())
.finalize()
.unwrap();
Expand Down Expand Up @@ -3290,7 +3307,7 @@ mod tests {
false,
)
.unwrap(),
"python3.9",
"libpython3.9",
);
assert_eq!(
super::default_lib_name_windows(
Expand All @@ -3302,7 +3319,7 @@ mod tests {
false,
)
.unwrap(),
"python3",
"libpython3",
);
assert_eq!(
super::default_lib_name_windows(
Expand Down Expand Up @@ -4114,8 +4131,20 @@ mod tests {
.unwrap();

let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap();
let win_gnu = Triple::from_str("x86_64-pc-windows-gnu").unwrap();
let win_gnu_x86 = Triple::from_str("i686-pc-windows-gnu").unwrap();
let win_gnullvm = Triple::from_str("x86_64-pc-windows-gnullvm").unwrap();
let win_gnullvm_x86 = Triple::from_str("i686-pc-windows-gnullvm").unwrap();
let win_gnullvm_arm64 = Triple::from_str("aarch64-pc-windows-gnullvm").unwrap();
let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap();
let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap();
let windows_gnu_like = [
&win_gnu,
&win_gnu_x86,
&win_gnullvm,
&win_gnullvm_x86,
&win_gnullvm_arm64,
];

let lib_name = default_lib_name_for_target(cpy39, &unix);
assert_eq!(lib_name, "python3.9");
Expand All @@ -4126,13 +4155,23 @@ mod tests {
let lib_name = default_lib_name_for_target(cpy39, &win_arm64);
assert_eq!(lib_name, "python39");

for target in windows_gnu_like {
let lib_name = default_lib_name_for_target(cpy39, target);
assert_eq!(lib_name, "libpython3.9");
}

// PyPy
let lib_name = default_lib_name_for_target(pypy311, &unix);
assert_eq!(lib_name, "pypy3.11-c");

let lib_name = default_lib_name_for_target(pypy311, &win_x64);
assert_eq!(lib_name, "libpypy3.11-c");

for target in windows_gnu_like {
let lib_name = default_lib_name_for_target(pypy311, target);
assert_eq!(lib_name, "libpypy3.11-c");
}

// Free-threaded
let lib_name = default_lib_name_for_target(cpy313t, &unix);
assert_eq!(lib_name, "python3.13t");
Expand All @@ -4150,6 +4189,11 @@ mod tests {
let lib_name = default_lib_name_for_target(cpy313_abi3, &win_x64);
assert_eq!(lib_name, "python3");

for target in windows_gnu_like {
let lib_name = default_lib_name_for_target(cpy313_abi3, target);
assert_eq!(lib_name, "libpython3");
}

let lib_name = default_lib_name_for_target(cpy313_abi3, &win_arm64);
assert_eq!(lib_name, "python3");
}
Expand Down
16 changes: 12 additions & 4 deletions pyo3-build-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,21 @@ pub fn print_expected_cfgs() {
"python3_d".to_string(),
"python3t".to_string(),
"python3t_d".to_string(),
"libpython3".to_string(),
"libpython3_d".to_string(),
"libpython3t".to_string(),
"libpython3t_d".to_string(),
];
for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::STABLE_ABI_MAX_MINOR + 1 {
dll_names.push(format!("python3{i}"));
dll_names.push(format!("python3{i}_d"));
dll_names.push(format!("libpython3.{i}"));
dll_names.push(format!("libpython3.{i}_d"));
if i >= 13 {
dll_names.push(format!("python3{i}t"));
dll_names.push(format!("python3{i}t_d"));
dll_names.push(format!("libpython3.{i}t"));
dll_names.push(format!("libpython3.{i}t_d"));
}
}
// PyPy DLL names (libpypy3.X-c.dll)
Expand Down Expand Up @@ -324,7 +332,7 @@ pub mod pyo3_build_script_impl {
target.architecture,
Architecture::Wasm32 | Architecture::Wasm64
);
let is_emscripten = target.operating_system == target_lexicon::OperatingSystem::Emscripten;
let is_emscripten = target.operating_system == OperatingSystem::Emscripten;
// webassembly targets generally don't support rpath, emscripten is the only exception currently aware of:
// https://github.com/emscripten-core/emscripten/issues/22126
if is_linking_libpython && (!is_wasm || is_emscripten) {
Expand Down Expand Up @@ -468,15 +476,15 @@ mod tests {
interpreter_config.to_writer(&mut buf).unwrap();
let config_string = escape(&buf);
// SAFETY: no other tests use `crate::get()`
unsafe { std::env::set_var(InterpreterConfig::PYO3_FFI_CONFIG_ENV_VAR, &config_string) };
unsafe { env::set_var(InterpreterConfig::PYO3_FFI_CONFIG_ENV_VAR, &config_string) };

assert_eq!(get_inner(), interpreter_config);

// Repeat with PyO3 env var
// SAFETY: no other tests use `crate::get()`
unsafe {
std::env::remove_var(InterpreterConfig::PYO3_FFI_CONFIG_ENV_VAR);
std::env::set_var(InterpreterConfig::PYO3_CONFIG_ENV_VAR, &config_string)
env::remove_var(InterpreterConfig::PYO3_FFI_CONFIG_ENV_VAR);
env::set_var(InterpreterConfig::PYO3_CONFIG_ENV_VAR, &config_string)
}

assert_eq!(get_inner(), interpreter_config);
Expand Down
59 changes: 59 additions & 0 deletions pyo3-ffi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,23 @@ fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result
Ok(())
}

fn windows_gnu_import_lib_name<'a>(
target_env: Option<&str>,
raw_dylib_name: &'a str,
) -> Option<&'a str> {
let is_gnu = matches!(target_env, Some("gnu" | "gnullvm"));

if !is_gnu {
return None;
}

Some(raw_dylib_name.strip_prefix("lib").unwrap_or(raw_dylib_name))
}

fn emit_link_config(build_config: &BuildConfig) -> Result<()> {
let interpreter_config = &build_config.interpreter_config;
let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = cargo_env_var("CARGO_CFG_TARGET_ENV");

let lib_name = interpreter_config
.lib_name()
Expand All @@ -208,6 +222,24 @@ fn emit_link_config(build_config: &BuildConfig) -> Result<()> {
// Python interpreter on Windows is not supported by this path (and is not
// officially supported by CPython on Windows).
println!("cargo:rustc-cfg=pyo3_dll=\"{lib_name}\"");

if let Some(import_lib_name) = windows_gnu_import_lib_name(target_env.as_deref(), lib_name)
{
// GNU-family Windows targets still need an import library for any non-Rust
// objects participating in the final link, such as CFFI-generated C code.
println!("cargo:rustc-link-lib={import_lib_name}");

if let Some(lib_dir) = interpreter_config.lib_dir() {
println!("cargo:rustc-link-search=native={lib_dir}");
} else if matches!(build_config.source, BuildConfigSource::CrossCompile) {
warn!(
"The output binary will link to libpython, \
but PYO3_CROSS_LIB_DIR environment variable is not set. \
Ensure that the target Python library directory is \
in the rustc native library search path."
);
}
}
} else {
println!(
"cargo:rustc-link-lib={link_model}{lib_name}",
Expand Down Expand Up @@ -294,3 +326,30 @@ fn main() {
std::process::exit(1)
}
}

#[cfg(test)]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as if this test weren't necessary either

mod tests {
use super::windows_gnu_import_lib_name;

#[test]
fn windows_gnu_import_lib_name_for_gnu_like_targets() {
assert_eq!(
windows_gnu_import_lib_name(Some("gnu"), "libpython3.14"),
Some("python3.14")
);
assert_eq!(
windows_gnu_import_lib_name(Some("gnullvm"), "libpython3"),
Some("python3")
);
assert_eq!(
windows_gnu_import_lib_name(Some("gnu"), "libpypy3.11-c"),
Some("pypy3.11-c")
);
}

#[test]
fn windows_gnu_import_lib_name_not_needed_elsewhere() {
assert_eq!(windows_gnu_import_lib_name(Some("msvc"), "python314"), None);
assert_eq!(windows_gnu_import_lib_name(None, "python314"), None);
}
}
27 changes: 14 additions & 13 deletions pyo3-ffi/src/impl_/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ macro_rules! extern_libpython_items {
/// Helper macro to declare `extern` blocks that link against libpython on Windows
/// using `raw-dylib`, eliminating the need for import libraries.
///
/// The build script sets a `pyo3_dll` cfg value to the target DLL name (e.g. `python312`),
/// The build script sets a `pyo3_dll` cfg value to the raw-dylib link name
/// (e.g. `python312` or `libpython3.12`),
/// and this macro expands to the appropriate `#[link(name = "...", kind = "raw-dylib")]`
/// attribute for that DLL.
///
Expand All @@ -257,21 +258,21 @@ macro_rules! extern_libpython {
($abi:literal { $($body:tt)* }) => {
extern_libpython!(@impl $abi { $($body)* }
// abi3
"python3", "python3_d",
"python3", "python3_d", "libpython3", "libpython3_d",
// abi3t
"python3t", "python3t_d",
"python3t", "python3t_d", "libpython3t", "libpython3t_d",
// Python 3.9 - 3.15
"python39", "python39_d",
"python310", "python310_d",
"python311", "python311_d",
"python312", "python312_d",
"python313", "python313_d",
"python314", "python314_d",
"python315", "python315_d",
"python39", "python39_d", "libpython3.9", "libpython3.9_d",
"python310", "python310_d", "libpython3.10", "libpython3.10_d",
"python311", "python311_d", "libpython3.11", "libpython3.11_d",
"python312", "python312_d", "libpython3.12", "libpython3.12_d",
"python313", "python313_d", "libpython3.13", "libpython3.13_d",
"python314", "python314_d", "libpython3.14", "libpython3.14_d",
"python315", "python315_d", "libpython3.15", "libpython3.15_d",
// free-threaded builds (3.13+)
"python313t", "python313t_d",
"python314t", "python314t_d",
"python315t", "python315t_d",
"python313t", "python313t_d", "libpython3.13t", "libpython3.13t_d",
"python314t", "python314t_d", "libpython3.14t", "libpython3.14t_d",
"python315t", "python315t_d", "libpython3.15t", "libpython3.15t_d",
// PyPy (DLL is libpypy3.X-c.dll, not pythonXY.dll)
"libpypy3.11-c",
);
Expand Down
Loading