From 18d1ac85ad0be9c1b1a894856b3f72a6ea3acf15 Mon Sep 17 00:00:00 2001 From: messense Date: Mon, 9 May 2022 21:02:28 +0800 Subject: [PATCH 1/2] Add support for generating non-abi3 `pythonXY.dll` --- README.md | 2 +- src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c10dae3..6c01326 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ fn main() { let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); let libdir = std::path::Path::new(&cross_lib_dir); - python3_dll_a::generate_implib_for_target(libdir, &arch, &env) + python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env) .expect("python3.dll import library generator failed"); } } diff --git a/src/lib.rs b/src/lib.rs index 0034a1b..ce21ae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); //! //! let libdir = std::path::Path::new(&cross_lib_dir); -//! python3_dll_a::generate_implib_for_target(libdir, &arch, &env) +//! python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env) //! .expect("python3.dll import library generator failed"); //! } //! } @@ -80,17 +80,11 @@ use std::io::{Error, ErrorKind, Result}; use std::path::Path; use std::process::Command; -/// Module-Definition file name for `python3.dll` -const DEF_FILE: &str = "python3.def"; +/// Import library file extension for the GNU environment ABI (MinGW-w64) +const IMPLIB_EXT_GNU: &str = ".dll.a"; -/// Module-Definition file content for `python3.dll` -const DEF_FILE_CONTENT: &[u8] = include_bytes!("python3.def"); - -/// Canonical `python3.dll` import library file name for the GNU environment ABI (MinGW-w64) -const IMPLIB_FILE_GNU: &str = "python3.dll.a"; - -/// Canonical `python3.dll` import library file name for the MSVC environment ABI -const IMPLIB_FILE_MSVC: &str = "python3.lib"; +/// Import library file extension for the MSVC environment ABI +const IMPLIB_EXT_MSVC: &str = ".lib"; /// Canonical MinGW-w64 `dlltool` program name const DLLTOOL_GNU: &str = "x86_64-w64-mingw32-dlltool"; @@ -115,26 +109,52 @@ const LIB_MSVC: &str = "lib.exe"; /// /// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`) /// is passed in `env`. -pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Result<()> { +pub fn generate_implib_for_target( + version: Option<(u8, u8)>, + out_dir: &Path, + arch: &str, + env: &str, +) -> Result<()> { create_dir_all(out_dir)?; let mut defpath = out_dir.to_owned(); - defpath.push(DEF_FILE); + let (def_file, def_file_content) = match version { + None => ("python3.def", include_str!("python3.def")), + Some((3, 7)) => ("python37.def", include_str!("python37.def")), + Some((3, 8)) => ("python38.def", include_str!("python38.def")), + Some((3, 9)) => ("python39.def", include_str!("python39.def")), + Some((3, 10)) => ("python310.def", include_str!("python310.def")), + Some((3, 11)) => ("python311.def", include_str!("python311.def")), + _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python version")), + }; + defpath.push(def_file); - write(&defpath, DEF_FILE_CONTENT)?; + write(&defpath, def_file_content)?; // Try to guess the `dlltool` executable name from the target triple. - let (command, dlltool) = match (arch, env) { + let (command, dlltool, implib_file) = match (arch, env) { // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu) - ("x86_64", "gnu") => (Command::new(DLLTOOL_GNU), DLLTOOL_GNU), + ("x86_64", "gnu") => ( + Command::new(DLLTOOL_GNU), + DLLTOOL_GNU, + def_file.replace(".def", IMPLIB_EXT_GNU), + ), // 32-bit MinGW-w64 (aka i686-pc-windows-gnu) - ("x86", "gnu") => (Command::new(DLLTOOL_GNU_32), DLLTOOL_GNU_32), + ("x86", "gnu") => ( + Command::new(DLLTOOL_GNU_32), + DLLTOOL_GNU_32, + def_file.replace(".def", IMPLIB_EXT_GNU), + ), // MSVC ABI (multiarch) (_, "msvc") => { if let Some(command) = find_lib_exe(arch) { - (command, LIB_MSVC) + (command, LIB_MSVC, def_file.replace(".def", IMPLIB_EXT_MSVC)) } else { - (Command::new(DLLTOOL_MSVC), DLLTOOL_MSVC) + ( + Command::new(DLLTOOL_MSVC), + DLLTOOL_MSVC, + def_file.replace(".def", IMPLIB_EXT_MSVC), + ) } } _ => { @@ -144,7 +164,8 @@ pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Resu }; // Run the selected `dlltool` executable to generate the import library. - let status = build_dlltool_command(command, dlltool, arch, &defpath, out_dir).status()?; + let status = + build_dlltool_command(command, dlltool, &implib_file, arch, &defpath, out_dir).status()?; if status.success() { Ok(()) @@ -177,6 +198,7 @@ fn find_lib_exe(_arch: &str) -> Option { fn build_dlltool_command( mut command: Command, dlltool: &str, + implib_file: &str, arch: &str, defpath: &Path, out_dir: &Path, @@ -185,7 +207,7 @@ fn build_dlltool_command( // Check whether we are using LLVM `dlltool` or MinGW `dlltool`. if dlltool == DLLTOOL_MSVC { - libpath.push(IMPLIB_FILE_MSVC); + libpath.push(implib_file); // LLVM tools use their own target architecture names... let machine = match arch { @@ -203,7 +225,7 @@ fn build_dlltool_command( .arg("-l") .arg(libpath); } else if dlltool == LIB_MSVC { - libpath.push(IMPLIB_FILE_MSVC); + libpath.push(implib_file); // lib.exe use their own target architecure names... let machine = match arch { @@ -217,7 +239,7 @@ fn build_dlltool_command( .arg(format!("/DEF:{}", defpath.display())) .arg(format!("/OUT:{}", libpath.display())); } else { - libpath.push(IMPLIB_FILE_GNU); + libpath.push(implib_file); command .arg("--input-def") @@ -244,7 +266,12 @@ mod tests { dir.push("x86_64-pc-windows-gnu"); dir.push("python3-dll"); - generate_implib_for_target(&dir, "x86_64", "gnu").unwrap(); + generate_implib_for_target(None, &dir, "x86_64", "gnu").unwrap(); + generate_implib_for_target(Some((3, 7)), &dir, "x86_64", "gnu").unwrap(); + generate_implib_for_target(Some((3, 8)), &dir, "x86_64", "gnu").unwrap(); + generate_implib_for_target(Some((3, 9)), &dir, "x86_64", "gnu").unwrap(); + generate_implib_for_target(Some((3, 10)), &dir, "x86_64", "gnu").unwrap(); + generate_implib_for_target(Some((3, 11)), &dir, "x86_64", "gnu").unwrap(); } #[cfg(unix)] @@ -255,7 +282,7 @@ mod tests { dir.push("i686-pc-windows-gnu"); dir.push("python3-dll"); - generate_implib_for_target(&dir, "x86", "gnu").unwrap(); + generate_implib_for_target(None, &dir, "x86", "gnu").unwrap(); } #[test] @@ -265,7 +292,7 @@ mod tests { dir.push("x86_64-pc-windows-msvc"); dir.push("python3-dll"); - generate_implib_for_target(&dir, "x86_64", "msvc").unwrap(); + generate_implib_for_target(None, &dir, "x86_64", "msvc").unwrap(); } #[test] @@ -275,7 +302,7 @@ mod tests { dir.push("i686-pc-windows-msvc"); dir.push("python3-dll"); - generate_implib_for_target(&dir, "x86", "msvc").unwrap(); + generate_implib_for_target(None, &dir, "x86", "msvc").unwrap(); } #[test] @@ -285,6 +312,6 @@ mod tests { dir.push("aarch64-pc-windows-msvc"); dir.push("python3-dll"); - generate_implib_for_target(&dir, "aarch64", "msvc").unwrap(); + generate_implib_for_target(None, &dir, "aarch64", "msvc").unwrap(); } } From 9408f834e225841617d20e28e442c7f0f96f0103 Mon Sep 17 00:00:00 2001 From: messense Date: Tue, 10 May 2022 10:38:07 +0800 Subject: [PATCH 2/2] feat: Add `ImportLibraryGenerator` --- README.md | 2 +- src/lib.rs | 317 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 184 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 6c01326..c10dae3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ fn main() { let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); let libdir = std::path::Path::new(&cross_lib_dir); - python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env) + python3_dll_a::generate_implib_for_target(libdir, &arch, &env) .expect("python3.dll import library generator failed"); } } diff --git a/src/lib.rs b/src/lib.rs index ce21ae0..4944af2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap(); //! //! let libdir = std::path::Path::new(&cross_lib_dir); -//! python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env) +//! python3_dll_a::generate_implib_for_target(libdir, &arch, &env) //! .expect("python3.dll import library generator failed"); //! } //! } @@ -75,6 +75,7 @@ #![deny(missing_docs)] #![allow(clippy::needless_doctest_main)] +use std::ffi::OsStr; use std::fs::{create_dir_all, write}; use std::io::{Error, ErrorKind, Result}; use std::path::Path; @@ -98,6 +99,169 @@ const DLLTOOL_MSVC: &str = "llvm-dlltool"; /// Canonical `lib` program name for the MSVC environment ABI (MSVC lib.exe) const LIB_MSVC: &str = "lib.exe"; +/// Windows import library generator for Python +/// +/// Generates `python3.dll` or `pythonXY.dll` import library directly from the +/// embedded Python ABI definitions data for the specified compile target. +#[derive(Debug, Clone)] +pub struct ImportLibraryGenerator { + /// The compile target architecture name (as in `CARGO_CFG_TARGET_ARCH`) + arch: String, + // The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`) + env: String, + /// Python major and minor version + version: Option<(u8, u8)>, +} + +impl ImportLibraryGenerator { + /// Creates a new import library generator for the specified compile target + pub fn new(arch: &str, env: &str) -> Self { + Self { + arch: arch.to_string(), + env: env.to_string(), + version: None, + } + } + + /// Set python major and minor version + pub fn version(&mut self, version: Option<(u8, u8)>) -> &mut Self { + self.version = version; + self + } + + /// Generates the import library in `out_dir` + pub fn generate(&self, out_dir: &Path) -> Result<()> { + create_dir_all(out_dir)?; + + let mut defpath = out_dir.to_owned(); + let (def_file, def_file_content) = match self.version { + None => ("python3.def", include_str!("python3.def")), + Some((3, 7)) => ("python37.def", include_str!("python37.def")), + Some((3, 8)) => ("python38.def", include_str!("python38.def")), + Some((3, 9)) => ("python39.def", include_str!("python39.def")), + Some((3, 10)) => ("python310.def", include_str!("python310.def")), + Some((3, 11)) => ("python311.def", include_str!("python311.def")), + _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python version")), + }; + defpath.push(def_file); + + write(&defpath, def_file_content)?; + + // Try to guess the `dlltool` executable name from the target triple. + let mut command = match (self.arch.as_str(), self.env.as_str()) { + // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu) + ("x86_64", "gnu") => self.build_dlltool_command( + DLLTOOL_GNU, + &def_file.replace(".def", IMPLIB_EXT_GNU), + &defpath, + out_dir, + ), + // 32-bit MinGW-w64 (aka i686-pc-windows-gnu) + ("x86", "gnu") => self.build_dlltool_command( + DLLTOOL_GNU_32, + &def_file.replace(".def", IMPLIB_EXT_GNU), + &defpath, + out_dir, + ), + // MSVC ABI (multiarch) + (_, "msvc") => { + let implib_file = def_file.replace(".def", IMPLIB_EXT_MSVC); + if let Some(command) = find_lib_exe(&self.arch) { + self.build_dlltool_command( + command.get_program(), + &implib_file, + &defpath, + out_dir, + ) + } else { + self.build_dlltool_command(DLLTOOL_MSVC, &implib_file, &defpath, out_dir) + } + } + _ => { + let msg = format!( + "Unsupported target arch '{}' or env ABI '{}'", + self.arch, self.env + ); + return Err(Error::new(ErrorKind::Other, msg)); + } + }; + + // Run the selected `dlltool` executable to generate the import library. + let status = command.status()?; + + if status.success() { + Ok(()) + } else { + let msg = format!("{:?} failed with {}", command, status); + Err(Error::new(ErrorKind::Other, msg)) + } + } + + /// Generates the complete `dlltool` executable invocation command. + /// + /// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors. + fn build_dlltool_command( + &self, + dlltool: impl AsRef, + implib_file: &str, + defpath: &Path, + out_dir: &Path, + ) -> Command { + let dlltool = dlltool.as_ref(); + let mut libpath = out_dir.to_owned(); + let mut command = if self.env == "msvc" { + find_lib_exe(&self.arch).unwrap_or_else(|| Command::new(dlltool)) + } else { + Command::new(dlltool) + }; + + // Check whether we are using LLVM `dlltool` or MinGW `dlltool`. + if dlltool == DLLTOOL_MSVC { + libpath.push(implib_file); + + // LLVM tools use their own target architecture names... + let machine = match self.arch.as_str() { + "x86_64" => "i386:x86-64", + "x86" => "i386", + "aarch64" => "arm64", + arch => arch, + }; + + command + .arg("-m") + .arg(machine) + .arg("-d") + .arg(defpath) + .arg("-l") + .arg(libpath); + } else if Path::new(dlltool).file_name() == Some(LIB_MSVC.as_ref()) { + libpath.push(implib_file); + + // lib.exe use their own target architecure names... + let machine = match self.arch.as_str() { + "x86_64" => "X64", + "x86" => "X86", + "aarch64" => "ARM64", + arch => arch, + }; + command + .arg(format!("/MACHINE:{}", machine)) + .arg(format!("/DEF:{}", defpath.display())) + .arg(format!("/OUT:{}", libpath.display())); + } else { + libpath.push(implib_file); + + command + .arg("--input-def") + .arg(defpath) + .arg("--output-lib") + .arg(libpath); + } + + command + } +} + /// Generates `python3.dll` import library directly from the embedded /// Python Stable ABI definitions data for the specified compile target. /// @@ -109,70 +273,8 @@ const LIB_MSVC: &str = "lib.exe"; /// /// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`) /// is passed in `env`. -pub fn generate_implib_for_target( - version: Option<(u8, u8)>, - out_dir: &Path, - arch: &str, - env: &str, -) -> Result<()> { - create_dir_all(out_dir)?; - - let mut defpath = out_dir.to_owned(); - let (def_file, def_file_content) = match version { - None => ("python3.def", include_str!("python3.def")), - Some((3, 7)) => ("python37.def", include_str!("python37.def")), - Some((3, 8)) => ("python38.def", include_str!("python38.def")), - Some((3, 9)) => ("python39.def", include_str!("python39.def")), - Some((3, 10)) => ("python310.def", include_str!("python310.def")), - Some((3, 11)) => ("python311.def", include_str!("python311.def")), - _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python version")), - }; - defpath.push(def_file); - - write(&defpath, def_file_content)?; - - // Try to guess the `dlltool` executable name from the target triple. - let (command, dlltool, implib_file) = match (arch, env) { - // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu) - ("x86_64", "gnu") => ( - Command::new(DLLTOOL_GNU), - DLLTOOL_GNU, - def_file.replace(".def", IMPLIB_EXT_GNU), - ), - // 32-bit MinGW-w64 (aka i686-pc-windows-gnu) - ("x86", "gnu") => ( - Command::new(DLLTOOL_GNU_32), - DLLTOOL_GNU_32, - def_file.replace(".def", IMPLIB_EXT_GNU), - ), - // MSVC ABI (multiarch) - (_, "msvc") => { - if let Some(command) = find_lib_exe(arch) { - (command, LIB_MSVC, def_file.replace(".def", IMPLIB_EXT_MSVC)) - } else { - ( - Command::new(DLLTOOL_MSVC), - DLLTOOL_MSVC, - def_file.replace(".def", IMPLIB_EXT_MSVC), - ) - } - } - _ => { - let msg = format!("Unsupported target arch '{arch}' or env ABI '{env}'"); - return Err(Error::new(ErrorKind::Other, msg)); - } - }; - - // Run the selected `dlltool` executable to generate the import library. - let status = - build_dlltool_command(command, dlltool, &implib_file, arch, &defpath, out_dir).status()?; - - if status.success() { - Ok(()) - } else { - let msg = format!("{dlltool} failed with {status}"); - Err(Error::new(ErrorKind::Other, msg)) - } +pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Result<()> { + ImportLibraryGenerator::new(arch, env).generate(out_dir) } /// Find Visual Studio lib.exe on Windows @@ -192,65 +294,6 @@ fn find_lib_exe(_arch: &str) -> Option { None } -/// Generates the complete `dlltool` executable invocation command. -/// -/// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors. -fn build_dlltool_command( - mut command: Command, - dlltool: &str, - implib_file: &str, - arch: &str, - defpath: &Path, - out_dir: &Path, -) -> Command { - let mut libpath = out_dir.to_owned(); - - // Check whether we are using LLVM `dlltool` or MinGW `dlltool`. - if dlltool == DLLTOOL_MSVC { - libpath.push(implib_file); - - // LLVM tools use their own target architecture names... - let machine = match arch { - "x86_64" => "i386:x86-64", - "x86" => "i386", - "aarch64" => "arm64", - _ => arch, - }; - - command - .arg("-m") - .arg(machine) - .arg("-d") - .arg(defpath) - .arg("-l") - .arg(libpath); - } else if dlltool == LIB_MSVC { - libpath.push(implib_file); - - // lib.exe use their own target architecure names... - let machine = match arch { - "x86_64" => "X64", - "x86" => "X86", - "aarch64" => "ARM64", - _ => arch, - }; - command - .arg(format!("/MACHINE:{}", machine)) - .arg(format!("/DEF:{}", defpath.display())) - .arg(format!("/OUT:{}", libpath.display())); - } else { - libpath.push(implib_file); - - command - .arg("--input-def") - .arg(defpath) - .arg("--output-lib") - .arg(libpath); - } - - command -} - #[cfg(test)] mod tests { use std::path::PathBuf; @@ -266,12 +309,12 @@ mod tests { dir.push("x86_64-pc-windows-gnu"); dir.push("python3-dll"); - generate_implib_for_target(None, &dir, "x86_64", "gnu").unwrap(); - generate_implib_for_target(Some((3, 7)), &dir, "x86_64", "gnu").unwrap(); - generate_implib_for_target(Some((3, 8)), &dir, "x86_64", "gnu").unwrap(); - generate_implib_for_target(Some((3, 9)), &dir, "x86_64", "gnu").unwrap(); - generate_implib_for_target(Some((3, 10)), &dir, "x86_64", "gnu").unwrap(); - generate_implib_for_target(Some((3, 11)), &dir, "x86_64", "gnu").unwrap(); + for minor in 7..=11 { + ImportLibraryGenerator::new("x86_64", "gnu") + .version(Some((3, minor))) + .generate(&dir) + .unwrap(); + } } #[cfg(unix)] @@ -282,7 +325,7 @@ mod tests { dir.push("i686-pc-windows-gnu"); dir.push("python3-dll"); - generate_implib_for_target(None, &dir, "x86", "gnu").unwrap(); + generate_implib_for_target(&dir, "x86", "gnu").unwrap(); } #[test] @@ -292,7 +335,13 @@ mod tests { dir.push("x86_64-pc-windows-msvc"); dir.push("python3-dll"); - generate_implib_for_target(None, &dir, "x86_64", "msvc").unwrap(); + generate_implib_for_target(&dir, "x86_64", "msvc").unwrap(); + for minor in 7..=11 { + ImportLibraryGenerator::new("x86_64", "msvc") + .version(Some((3, minor))) + .generate(&dir) + .unwrap(); + } } #[test] @@ -302,7 +351,7 @@ mod tests { dir.push("i686-pc-windows-msvc"); dir.push("python3-dll"); - generate_implib_for_target(None, &dir, "x86", "msvc").unwrap(); + generate_implib_for_target(&dir, "x86", "msvc").unwrap(); } #[test] @@ -312,6 +361,6 @@ mod tests { dir.push("aarch64-pc-windows-msvc"); dir.push("python3-dll"); - generate_implib_for_target(None, &dir, "aarch64", "msvc").unwrap(); + generate_implib_for_target(&dir, "aarch64", "msvc").unwrap(); } }