diff --git a/noxfile.py b/noxfile.py index 310f543304d..86291f19838 100644 --- a/noxfile.py +++ b/noxfile.py @@ -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") @@ -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 diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 0a706fcdceb..486d4e6cda5 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -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, @@ -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 { + // 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 @@ -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) @@ -2340,14 +2348,17 @@ 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")] { @@ -2355,19 +2366,25 @@ fn default_lib_name_windows(abi: PythonAbi, mingw: bool, debug: bool) -> Result< } 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 + )) } } @@ -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(); @@ -3290,7 +3307,7 @@ mod tests { false, ) .unwrap(), - "python3.9", + "libpython3.9", ); assert_eq!( super::default_lib_name_windows( @@ -3302,7 +3319,7 @@ mod tests { false, ) .unwrap(), - "python3", + "libpython3", ); assert_eq!( super::default_lib_name_windows( @@ -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"); @@ -4126,6 +4155,11 @@ 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"); @@ -4133,6 +4167,11 @@ mod tests { 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"); @@ -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"); } diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index ed23f20a789..4ceec95b314 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -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) @@ -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) { @@ -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); diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index a11ed75530b..bb644c7952a 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -194,6 +194,8 @@ fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result 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 target_arch = cargo_env_var("CARGO_CFG_TARGET_ARCH"); let lib_name = interpreter_config .lib_name() @@ -208,6 +210,25 @@ 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}\""); + + let is_i686_pc_windows_gnu = matches!(target_env.as_deref(), Some("gnu")) + && matches!(target_arch.as_deref(), Some("x86")); + + if is_i686_pc_windows_gnu { + let import_lib_name = lib_name.strip_prefix("lib").unwrap_or(lib_name); + 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}", diff --git a/pyo3-ffi/src/impl_/macros.rs b/pyo3-ffi/src/impl_/macros.rs index 5b2fd448876..a7e084d81ec 100644 --- a/pyo3-ffi/src/impl_/macros.rs +++ b/pyo3-ffi/src/impl_/macros.rs @@ -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. /// @@ -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", );