5656//! let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
5757//!
5858//! let libdir = std::path::Path::new(&cross_lib_dir);
59- //! python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env)
59+ //! python3_dll_a::generate_implib_for_target(libdir, &arch, &env)
6060//! .expect("python3.dll import library generator failed");
6161//! }
6262//! }
7575#![ deny( missing_docs) ]
7676#![ allow( clippy:: needless_doctest_main) ]
7777
78+ use std:: ffi:: OsStr ;
7879use std:: fs:: { create_dir_all, write} ;
7980use std:: io:: { Error , ErrorKind , Result } ;
8081use std:: path:: Path ;
@@ -98,6 +99,169 @@ const DLLTOOL_MSVC: &str = "llvm-dlltool";
9899/// Canonical `lib` program name for the MSVC environment ABI (MSVC lib.exe)
99100const LIB_MSVC : & str = "lib.exe" ;
100101
102+ /// Windows import library generator for Python
103+ ///
104+ /// Generates `python3.dll` or `pythonXY.dll` import library directly from the
105+ /// embedded Python ABI definitions data for the specified compile target.
106+ #[ derive( Debug , Clone ) ]
107+ pub struct ImportLibraryGenerator {
108+ /// The compile target architecture name (as in `CARGO_CFG_TARGET_ARCH`)
109+ arch : String ,
110+ // The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
111+ env : String ,
112+ /// Python major and minor version
113+ version : Option < ( u8 , u8 ) > ,
114+ }
115+
116+ impl ImportLibraryGenerator {
117+ /// Creates a new import library generator for the specified compile target
118+ pub fn new ( arch : & str , env : & str ) -> Self {
119+ Self {
120+ arch : arch. to_string ( ) ,
121+ env : env. to_string ( ) ,
122+ version : None ,
123+ }
124+ }
125+
126+ /// Set python major and minor version
127+ pub fn version ( & mut self , version : Option < ( u8 , u8 ) > ) -> & mut Self {
128+ self . version = version;
129+ self
130+ }
131+
132+ /// Generates the import library in `out_dir`
133+ pub fn generate ( & self , out_dir : & Path ) -> Result < ( ) > {
134+ create_dir_all ( out_dir) ?;
135+
136+ let mut defpath = out_dir. to_owned ( ) ;
137+ let ( def_file, def_file_content) = match self . version {
138+ None => ( "python3.def" , include_str ! ( "python3.def" ) ) ,
139+ Some ( ( 3 , 7 ) ) => ( "python37.def" , include_str ! ( "python37.def" ) ) ,
140+ Some ( ( 3 , 8 ) ) => ( "python38.def" , include_str ! ( "python38.def" ) ) ,
141+ Some ( ( 3 , 9 ) ) => ( "python39.def" , include_str ! ( "python39.def" ) ) ,
142+ Some ( ( 3 , 10 ) ) => ( "python310.def" , include_str ! ( "python310.def" ) ) ,
143+ Some ( ( 3 , 11 ) ) => ( "python311.def" , include_str ! ( "python311.def" ) ) ,
144+ _ => return Err ( Error :: new ( ErrorKind :: Other , "Unsupported Python version" ) ) ,
145+ } ;
146+ defpath. push ( def_file) ;
147+
148+ write ( & defpath, def_file_content) ?;
149+
150+ // Try to guess the `dlltool` executable name from the target triple.
151+ let mut command = match ( self . arch . as_str ( ) , self . env . as_str ( ) ) {
152+ // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu)
153+ ( "x86_64" , "gnu" ) => self . build_dlltool_command (
154+ DLLTOOL_GNU ,
155+ & def_file. replace ( ".def" , IMPLIB_EXT_GNU ) ,
156+ & defpath,
157+ out_dir,
158+ ) ,
159+ // 32-bit MinGW-w64 (aka i686-pc-windows-gnu)
160+ ( "x86" , "gnu" ) => self . build_dlltool_command (
161+ DLLTOOL_GNU_32 ,
162+ & def_file. replace ( ".def" , IMPLIB_EXT_GNU ) ,
163+ & defpath,
164+ out_dir,
165+ ) ,
166+ // MSVC ABI (multiarch)
167+ ( _, "msvc" ) => {
168+ let implib_file = def_file. replace ( ".def" , IMPLIB_EXT_MSVC ) ;
169+ if let Some ( command) = find_lib_exe ( & self . arch ) {
170+ self . build_dlltool_command (
171+ command. get_program ( ) ,
172+ & implib_file,
173+ & defpath,
174+ out_dir,
175+ )
176+ } else {
177+ self . build_dlltool_command ( DLLTOOL_MSVC , & implib_file, & defpath, out_dir)
178+ }
179+ }
180+ _ => {
181+ let msg = format ! (
182+ "Unsupported target arch '{}' or env ABI '{}'" ,
183+ self . arch, self . env
184+ ) ;
185+ return Err ( Error :: new ( ErrorKind :: Other , msg) ) ;
186+ }
187+ } ;
188+
189+ // Run the selected `dlltool` executable to generate the import library.
190+ let status = command. status ( ) ?;
191+
192+ if status. success ( ) {
193+ Ok ( ( ) )
194+ } else {
195+ let msg = format ! ( "{:?} failed with {}" , command, status) ;
196+ Err ( Error :: new ( ErrorKind :: Other , msg) )
197+ }
198+ }
199+
200+ /// Generates the complete `dlltool` executable invocation command.
201+ ///
202+ /// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors.
203+ fn build_dlltool_command (
204+ & self ,
205+ dlltool : impl AsRef < OsStr > ,
206+ implib_file : & str ,
207+ defpath : & Path ,
208+ out_dir : & Path ,
209+ ) -> Command {
210+ let dlltool = dlltool. as_ref ( ) ;
211+ let mut libpath = out_dir. to_owned ( ) ;
212+ let mut command = if self . env == "msvc" {
213+ find_lib_exe ( & self . arch ) . unwrap_or_else ( || Command :: new ( dlltool) )
214+ } else {
215+ Command :: new ( dlltool)
216+ } ;
217+
218+ // Check whether we are using LLVM `dlltool` or MinGW `dlltool`.
219+ if dlltool == DLLTOOL_MSVC {
220+ libpath. push ( implib_file) ;
221+
222+ // LLVM tools use their own target architecture names...
223+ let machine = match self . arch . as_str ( ) {
224+ "x86_64" => "i386:x86-64" ,
225+ "x86" => "i386" ,
226+ "aarch64" => "arm64" ,
227+ arch => arch,
228+ } ;
229+
230+ command
231+ . arg ( "-m" )
232+ . arg ( machine)
233+ . arg ( "-d" )
234+ . arg ( defpath)
235+ . arg ( "-l" )
236+ . arg ( libpath) ;
237+ } else if Path :: new ( dlltool) . file_name ( ) == Some ( LIB_MSVC . as_ref ( ) ) {
238+ libpath. push ( implib_file) ;
239+
240+ // lib.exe use their own target architecure names...
241+ let machine = match self . arch . as_str ( ) {
242+ "x86_64" => "X64" ,
243+ "x86" => "X86" ,
244+ "aarch64" => "ARM64" ,
245+ arch => arch,
246+ } ;
247+ command
248+ . arg ( format ! ( "/MACHINE:{}" , machine) )
249+ . arg ( format ! ( "/DEF:{}" , defpath. display( ) ) )
250+ . arg ( format ! ( "/OUT:{}" , libpath. display( ) ) ) ;
251+ } else {
252+ libpath. push ( implib_file) ;
253+
254+ command
255+ . arg ( "--input-def" )
256+ . arg ( defpath)
257+ . arg ( "--output-lib" )
258+ . arg ( libpath) ;
259+ }
260+
261+ command
262+ }
263+ }
264+
101265/// Generates `python3.dll` import library directly from the embedded
102266/// Python Stable ABI definitions data for the specified compile target.
103267///
@@ -109,70 +273,8 @@ const LIB_MSVC: &str = "lib.exe";
109273///
110274/// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
111275/// is passed in `env`.
112- pub fn generate_implib_for_target (
113- version : Option < ( u8 , u8 ) > ,
114- out_dir : & Path ,
115- arch : & str ,
116- env : & str ,
117- ) -> Result < ( ) > {
118- create_dir_all ( out_dir) ?;
119-
120- let mut defpath = out_dir. to_owned ( ) ;
121- let ( def_file, def_file_content) = match version {
122- None => ( "python3.def" , include_str ! ( "python3.def" ) ) ,
123- Some ( ( 3 , 7 ) ) => ( "python37.def" , include_str ! ( "python37.def" ) ) ,
124- Some ( ( 3 , 8 ) ) => ( "python38.def" , include_str ! ( "python38.def" ) ) ,
125- Some ( ( 3 , 9 ) ) => ( "python39.def" , include_str ! ( "python39.def" ) ) ,
126- Some ( ( 3 , 10 ) ) => ( "python310.def" , include_str ! ( "python310.def" ) ) ,
127- Some ( ( 3 , 11 ) ) => ( "python311.def" , include_str ! ( "python311.def" ) ) ,
128- _ => return Err ( Error :: new ( ErrorKind :: Other , "Unsupported Python version" ) ) ,
129- } ;
130- defpath. push ( def_file) ;
131-
132- write ( & defpath, def_file_content) ?;
133-
134- // Try to guess the `dlltool` executable name from the target triple.
135- let ( command, dlltool, implib_file) = match ( arch, env) {
136- // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu)
137- ( "x86_64" , "gnu" ) => (
138- Command :: new ( DLLTOOL_GNU ) ,
139- DLLTOOL_GNU ,
140- def_file. replace ( ".def" , IMPLIB_EXT_GNU ) ,
141- ) ,
142- // 32-bit MinGW-w64 (aka i686-pc-windows-gnu)
143- ( "x86" , "gnu" ) => (
144- Command :: new ( DLLTOOL_GNU_32 ) ,
145- DLLTOOL_GNU_32 ,
146- def_file. replace ( ".def" , IMPLIB_EXT_GNU ) ,
147- ) ,
148- // MSVC ABI (multiarch)
149- ( _, "msvc" ) => {
150- if let Some ( command) = find_lib_exe ( arch) {
151- ( command, LIB_MSVC , def_file. replace ( ".def" , IMPLIB_EXT_MSVC ) )
152- } else {
153- (
154- Command :: new ( DLLTOOL_MSVC ) ,
155- DLLTOOL_MSVC ,
156- def_file. replace ( ".def" , IMPLIB_EXT_MSVC ) ,
157- )
158- }
159- }
160- _ => {
161- let msg = format ! ( "Unsupported target arch '{arch}' or env ABI '{env}'" ) ;
162- return Err ( Error :: new ( ErrorKind :: Other , msg) ) ;
163- }
164- } ;
165-
166- // Run the selected `dlltool` executable to generate the import library.
167- let status =
168- build_dlltool_command ( command, dlltool, & implib_file, arch, & defpath, out_dir) . status ( ) ?;
169-
170- if status. success ( ) {
171- Ok ( ( ) )
172- } else {
173- let msg = format ! ( "{dlltool} failed with {status}" ) ;
174- Err ( Error :: new ( ErrorKind :: Other , msg) )
175- }
276+ pub fn generate_implib_for_target ( out_dir : & Path , arch : & str , env : & str ) -> Result < ( ) > {
277+ ImportLibraryGenerator :: new ( arch, env) . generate ( out_dir)
176278}
177279
178280/// Find Visual Studio lib.exe on Windows
@@ -192,65 +294,6 @@ fn find_lib_exe(_arch: &str) -> Option<Command> {
192294 None
193295}
194296
195- /// Generates the complete `dlltool` executable invocation command.
196- ///
197- /// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors.
198- fn build_dlltool_command (
199- mut command : Command ,
200- dlltool : & str ,
201- implib_file : & str ,
202- arch : & str ,
203- defpath : & Path ,
204- out_dir : & Path ,
205- ) -> Command {
206- let mut libpath = out_dir. to_owned ( ) ;
207-
208- // Check whether we are using LLVM `dlltool` or MinGW `dlltool`.
209- if dlltool == DLLTOOL_MSVC {
210- libpath. push ( implib_file) ;
211-
212- // LLVM tools use their own target architecture names...
213- let machine = match arch {
214- "x86_64" => "i386:x86-64" ,
215- "x86" => "i386" ,
216- "aarch64" => "arm64" ,
217- _ => arch,
218- } ;
219-
220- command
221- . arg ( "-m" )
222- . arg ( machine)
223- . arg ( "-d" )
224- . arg ( defpath)
225- . arg ( "-l" )
226- . arg ( libpath) ;
227- } else if dlltool == LIB_MSVC {
228- libpath. push ( implib_file) ;
229-
230- // lib.exe use their own target architecure names...
231- let machine = match arch {
232- "x86_64" => "X64" ,
233- "x86" => "X86" ,
234- "aarch64" => "ARM64" ,
235- _ => arch,
236- } ;
237- command
238- . arg ( format ! ( "/MACHINE:{}" , machine) )
239- . arg ( format ! ( "/DEF:{}" , defpath. display( ) ) )
240- . arg ( format ! ( "/OUT:{}" , libpath. display( ) ) ) ;
241- } else {
242- libpath. push ( implib_file) ;
243-
244- command
245- . arg ( "--input-def" )
246- . arg ( defpath)
247- . arg ( "--output-lib" )
248- . arg ( libpath) ;
249- }
250-
251- command
252- }
253-
254297#[ cfg( test) ]
255298mod tests {
256299 use std:: path:: PathBuf ;
@@ -266,12 +309,12 @@ mod tests {
266309 dir. push ( "x86_64-pc-windows-gnu" ) ;
267310 dir. push ( "python3-dll" ) ;
268311
269- generate_implib_for_target ( None , & dir , "x86_64" , "gnu" ) . unwrap ( ) ;
270- generate_implib_for_target ( Some ( ( 3 , 7 ) ) , & dir , "x86_64" , "gnu" ) . unwrap ( ) ;
271- generate_implib_for_target ( Some ( ( 3 , 8 ) ) , & dir , "x86_64" , "gnu" ) . unwrap ( ) ;
272- generate_implib_for_target ( Some ( ( 3 , 9 ) ) , & dir , "x86_64" , "gnu" ) . unwrap ( ) ;
273- generate_implib_for_target ( Some ( ( 3 , 10 ) ) , & dir , "x86_64" , "gnu" ) . unwrap ( ) ;
274- generate_implib_for_target ( Some ( ( 3 , 11 ) ) , & dir , "x86_64" , "gnu" ) . unwrap ( ) ;
312+ for minor in 7 ..= 11 {
313+ ImportLibraryGenerator :: new ( "x86_64" , "gnu" )
314+ . version ( Some ( ( 3 , minor ) ) )
315+ . generate ( & dir )
316+ . unwrap ( ) ;
317+ }
275318 }
276319
277320 #[ cfg( unix) ]
@@ -282,7 +325,7 @@ mod tests {
282325 dir. push ( "i686-pc-windows-gnu" ) ;
283326 dir. push ( "python3-dll" ) ;
284327
285- generate_implib_for_target ( None , & dir, "x86" , "gnu" ) . unwrap ( ) ;
328+ generate_implib_for_target ( & dir, "x86" , "gnu" ) . unwrap ( ) ;
286329 }
287330
288331 #[ test]
@@ -292,7 +335,13 @@ mod tests {
292335 dir. push ( "x86_64-pc-windows-msvc" ) ;
293336 dir. push ( "python3-dll" ) ;
294337
295- generate_implib_for_target ( None , & dir, "x86_64" , "msvc" ) . unwrap ( ) ;
338+ generate_implib_for_target ( & dir, "x86_64" , "msvc" ) . unwrap ( ) ;
339+ for minor in 7 ..=11 {
340+ ImportLibraryGenerator :: new ( "x86_64" , "msvc" )
341+ . version ( Some ( ( 3 , minor) ) )
342+ . generate ( & dir)
343+ . unwrap ( ) ;
344+ }
296345 }
297346
298347 #[ test]
@@ -302,7 +351,7 @@ mod tests {
302351 dir. push ( "i686-pc-windows-msvc" ) ;
303352 dir. push ( "python3-dll" ) ;
304353
305- generate_implib_for_target ( None , & dir, "x86" , "msvc" ) . unwrap ( ) ;
354+ generate_implib_for_target ( & dir, "x86" , "msvc" ) . unwrap ( ) ;
306355 }
307356
308357 #[ test]
@@ -312,6 +361,6 @@ mod tests {
312361 dir. push ( "aarch64-pc-windows-msvc" ) ;
313362 dir. push ( "python3-dll" ) ;
314363
315- generate_implib_for_target ( None , & dir, "aarch64" , "msvc" ) . unwrap ( ) ;
364+ generate_implib_for_target ( & dir, "aarch64" , "msvc" ) . unwrap ( ) ;
316365 }
317366}
0 commit comments