7676#![ deny( missing_docs) ]
7777#![ allow( clippy:: needless_doctest_main) ]
7878
79- use std:: ffi:: OsStr ;
8079use std:: fs:: { create_dir_all, write} ;
8180use std:: io:: { Error , ErrorKind , Result } ;
8281use std:: path:: { Path , PathBuf } ;
@@ -98,6 +97,7 @@ const DLLTOOL_GNU_32: &str = "i686-w64-mingw32-dlltool";
9897const DLLTOOL_MSVC : & str = "llvm-dlltool" ;
9998
10099/// Canonical `lib` program name for the MSVC environment ABI (MSVC lib.exe)
100+ #[ cfg( windows) ]
101101const LIB_MSVC : & str = "lib.exe" ;
102102
103103/// Windows import library generator for Python
@@ -150,27 +150,10 @@ impl ImportLibraryGenerator {
150150 let implib_file = self . implib_file_path ( out_dir) ;
151151
152152 // Try to guess the `dlltool` executable name from the target triple.
153- let mut command = match ( self . arch . as_str ( ) , self . env . as_str ( ) ) {
154- // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu)
155- ( "x86_64" , "gnu" ) => self . build_dlltool_command ( DLLTOOL_GNU , & implib_file, & defpath) ,
156- // 32-bit MinGW-w64 (aka i686-pc-windows-gnu)
157- ( "x86" , "gnu" ) => self . build_dlltool_command ( DLLTOOL_GNU_32 , & implib_file, & defpath) ,
158- // MSVC ABI (multiarch)
159- ( _, "msvc" ) => {
160- if let Some ( command) = find_lib_exe ( & self . arch ) {
161- self . build_dlltool_command ( command. get_program ( ) , & implib_file, & defpath)
162- } else {
163- self . build_dlltool_command ( DLLTOOL_MSVC , & implib_file, & defpath)
164- }
165- }
166- _ => {
167- let msg = format ! (
168- "Unsupported target arch '{}' or env ABI '{}'" ,
169- self . arch, self . env
170- ) ;
171- return Err ( Error :: new ( ErrorKind :: Other , msg) ) ;
172- }
173- } ;
153+ let dlltool_command = DllToolCommand :: find_for_target ( & self . arch , & self . env ) ?;
154+
155+ // Build the complete `dlltool` command with all required arguments.
156+ let mut command = dlltool_command. build ( & defpath, & implib_file) ;
174157
175158 // Run the selected `dlltool` executable to generate the import library.
176159 let status = command. status ( ) . map_err ( |e| {
@@ -230,62 +213,6 @@ impl ImportLibraryGenerator {
230213
231214 libpath
232215 }
233-
234- /// Generates the complete `dlltool` executable invocation command.
235- ///
236- /// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors.
237- fn build_dlltool_command (
238- & self ,
239- dlltool : impl AsRef < OsStr > ,
240- libpath : & Path ,
241- defpath : & Path ,
242- ) -> Command {
243- let dlltool = dlltool. as_ref ( ) ;
244- let mut command = if self . env == "msvc" {
245- find_lib_exe ( & self . arch ) . unwrap_or_else ( || Command :: new ( dlltool) )
246- } else {
247- Command :: new ( dlltool)
248- } ;
249-
250- // Check whether we are using LLVM `dlltool` or MinGW `dlltool`.
251- if dlltool == DLLTOOL_MSVC {
252- // LLVM tools use their own target architecture names...
253- let machine = match self . arch . as_str ( ) {
254- "x86_64" => "i386:x86-64" ,
255- "x86" => "i386" ,
256- "aarch64" => "arm64" ,
257- arch => arch,
258- } ;
259-
260- command
261- . arg ( "-m" )
262- . arg ( machine)
263- . arg ( "-d" )
264- . arg ( defpath)
265- . arg ( "-l" )
266- . arg ( libpath) ;
267- } else if Path :: new ( dlltool) . file_name ( ) == Some ( LIB_MSVC . as_ref ( ) ) {
268- // lib.exe use their own target architecure names...
269- let machine = match self . arch . as_str ( ) {
270- "x86_64" => "X64" ,
271- "x86" => "X86" ,
272- "aarch64" => "ARM64" ,
273- arch => arch,
274- } ;
275- command
276- . arg ( format ! ( "/MACHINE:{}" , machine) )
277- . arg ( format ! ( "/DEF:{}" , defpath. display( ) ) )
278- . arg ( format ! ( "/OUT:{}" , libpath. display( ) ) ) ;
279- } else {
280- command
281- . arg ( "--input-def" )
282- . arg ( defpath)
283- . arg ( "--output-lib" )
284- . arg ( libpath) ;
285- }
286-
287- command
288- }
289216}
290217
291218/// Generates `python3.dll` import library directly from the embedded
@@ -303,6 +230,128 @@ pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Resu
303230 ImportLibraryGenerator :: new ( arch, env) . generate ( out_dir)
304231}
305232
233+ /// `dlltool` utility command builder
234+ ///
235+ /// Supports Visual Studio `lib.exe`, MinGW, LLVM and Zig `dlltool` flavors.
236+ #[ derive( Debug ) ]
237+ enum DllToolCommand {
238+ /// MinGW `dlltool` program (with prefix)
239+ Mingw { command : Command } ,
240+ /// LLVM `llvm-dlltool` program (no prefix)
241+ Llvm { command : Command , machine : String } ,
242+ /// MSVC `lib.exe` program (no prefix)
243+ LibExe { command : Command , machine : String } ,
244+ /// `zig dlltool` wrapper (no prefix)
245+ #[ allow( dead_code) ]
246+ Zig { command : Command , machine : String } ,
247+ }
248+
249+ impl DllToolCommand {
250+ /// Attempts to find the best matching `dlltool` flavor for the target.
251+ fn find_for_target ( arch : & str , env : & str ) -> Result < DllToolCommand > {
252+ match ( arch, env) {
253+ // 64-bit MinGW-w64 (aka `x86_64-pc-windows-gnu`)
254+ ( "x86_64" , "gnu" ) => Ok ( DllToolCommand :: Mingw {
255+ command : Command :: new ( DLLTOOL_GNU ) ,
256+ } ) ,
257+
258+ // 32-bit MinGW-w64 (aka `i686-pc-windows-gnu`)
259+ ( "x86" , "gnu" ) => Ok ( DllToolCommand :: Mingw {
260+ command : Command :: new ( DLLTOOL_GNU_32 ) ,
261+ } ) ,
262+
263+ // MSVC ABI (multiarch)
264+ ( _, "msvc" ) => {
265+ if let Some ( command) = find_lib_exe ( arch) {
266+ // MSVC tools use their own target architecture names...
267+ let machine = match arch {
268+ "x86_64" => "X64" ,
269+ "x86" => "X86" ,
270+ "aarch64" => "ARM64" ,
271+ arch => arch,
272+ }
273+ . to_owned ( ) ;
274+
275+ Ok ( DllToolCommand :: LibExe { command, machine } )
276+ } else {
277+ // LLVM tools use their own target architecture names...
278+ let machine = match arch {
279+ "x86_64" => "i386:x86-64" ,
280+ "x86" => "i386" ,
281+ "aarch64" => "arm64" ,
282+ arch => arch,
283+ }
284+ . to_owned ( ) ;
285+
286+ let command = Command :: new ( DLLTOOL_MSVC ) ;
287+
288+ Ok ( DllToolCommand :: Llvm { command, machine } )
289+ }
290+ }
291+ _ => {
292+ let msg = format ! ( "Unsupported target arch '{}' or env ABI '{}'" , arch, env) ;
293+ Err ( Error :: new ( ErrorKind :: Other , msg) )
294+ }
295+ }
296+ }
297+
298+ /// Generates the complete `dlltool` executable invocation command.
299+ fn build ( self , defpath : & Path , libpath : & Path ) -> Command {
300+ match self {
301+ Self :: Mingw { mut command } => {
302+ command
303+ . arg ( "--input-def" )
304+ . arg ( defpath)
305+ . arg ( "--output-lib" )
306+ . arg ( libpath) ;
307+
308+ command
309+ }
310+ Self :: Llvm {
311+ mut command,
312+ machine,
313+ } => {
314+ command
315+ . arg ( "-m" )
316+ . arg ( machine)
317+ . arg ( "-d" )
318+ . arg ( defpath)
319+ . arg ( "-l" )
320+ . arg ( libpath) ;
321+
322+ command
323+ }
324+ Self :: LibExe {
325+ mut command,
326+ machine,
327+ } => {
328+ command
329+ . arg ( format ! ( "/MACHINE:{}" , machine) )
330+ . arg ( format ! ( "/DEF:{}" , defpath. display( ) ) )
331+ . arg ( format ! ( "/OUT:{}" , libpath. display( ) ) ) ;
332+
333+ command
334+ }
335+ Self :: Zig {
336+ mut command,
337+ machine,
338+ } => {
339+ // Same as `llvm-dlltool`, but invoked as `zig dlltool`.
340+ command
341+ . arg ( "dlltool" )
342+ . arg ( "-m" )
343+ . arg ( machine)
344+ . arg ( "-d" )
345+ . arg ( defpath)
346+ . arg ( "-l" )
347+ . arg ( libpath) ;
348+
349+ command
350+ }
351+ }
352+ }
353+ }
354+
306355/// Finds Visual Studio `lib.exe` when running on Windows.
307356#[ cfg( windows) ]
308357fn find_lib_exe ( arch : & str ) -> Option < Command > {
0 commit comments