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 ;
8182use std:: process:: Command ;
8283
83- /// Module-Definition file name for `python3.dll`
84- const DEF_FILE : & str = "python3.def " ;
84+ /// Import library file extension for the GNU environment ABI (MinGW-w64)
85+ const IMPLIB_EXT_GNU : & str = ".dll.a " ;
8586
86- /// Module-Definition file content for `python3.dll`
87- const DEF_FILE_CONTENT : & [ u8 ] = include_bytes ! ( "python3.def" ) ;
88-
89- /// Canonical `python3.dll` import library file name for the GNU environment ABI (MinGW-w64)
90- const IMPLIB_FILE_GNU : & str = "python3.dll.a" ;
91-
92- /// Canonical `python3.dll` import library file name for the MSVC environment ABI
93- const IMPLIB_FILE_MSVC : & str = "python3.lib" ;
87+ /// Import library file extension for the MSVC environment ABI
88+ const IMPLIB_EXT_MSVC : & str = ".lib" ;
9489
9590/// Canonical MinGW-w64 `dlltool` program name
9691const DLLTOOL_GNU : & str = "x86_64-w64-mingw32-dlltool" ;
@@ -104,6 +99,169 @@ const DLLTOOL_MSVC: &str = "llvm-dlltool";
10499/// Canonical `lib` program name for the MSVC environment ABI (MSVC lib.exe)
105100const LIB_MSVC : & str = "lib.exe" ;
106101
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+
107265/// Generates `python3.dll` import library directly from the embedded
108266/// Python Stable ABI definitions data for the specified compile target.
109267///
@@ -116,42 +274,7 @@ const LIB_MSVC: &str = "lib.exe";
116274/// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
117275/// is passed in `env`.
118276pub fn generate_implib_for_target ( out_dir : & Path , arch : & str , env : & str ) -> Result < ( ) > {
119- create_dir_all ( out_dir) ?;
120-
121- let mut defpath = out_dir. to_owned ( ) ;
122- defpath. push ( DEF_FILE ) ;
123-
124- write ( & defpath, DEF_FILE_CONTENT ) ?;
125-
126- // Try to guess the `dlltool` executable name from the target triple.
127- let ( command, dlltool) = match ( arch, env) {
128- // 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu)
129- ( "x86_64" , "gnu" ) => ( Command :: new ( DLLTOOL_GNU ) , DLLTOOL_GNU ) ,
130- // 32-bit MinGW-w64 (aka i686-pc-windows-gnu)
131- ( "x86" , "gnu" ) => ( Command :: new ( DLLTOOL_GNU_32 ) , DLLTOOL_GNU_32 ) ,
132- // MSVC ABI (multiarch)
133- ( _, "msvc" ) => {
134- if let Some ( command) = find_lib_exe ( arch) {
135- ( command, LIB_MSVC )
136- } else {
137- ( Command :: new ( DLLTOOL_MSVC ) , DLLTOOL_MSVC )
138- }
139- }
140- _ => {
141- let msg = format ! ( "Unsupported target arch '{arch}' or env ABI '{env}'" ) ;
142- return Err ( Error :: new ( ErrorKind :: Other , msg) ) ;
143- }
144- } ;
145-
146- // Run the selected `dlltool` executable to generate the import library.
147- let status = build_dlltool_command ( command, dlltool, arch, & defpath, out_dir) . status ( ) ?;
148-
149- if status. success ( ) {
150- Ok ( ( ) )
151- } else {
152- let msg = format ! ( "{dlltool} failed with {status}" ) ;
153- Err ( Error :: new ( ErrorKind :: Other , msg) )
154- }
277+ ImportLibraryGenerator :: new ( arch, env) . generate ( out_dir)
155278}
156279
157280/// Find Visual Studio lib.exe on Windows
@@ -171,64 +294,6 @@ fn find_lib_exe(_arch: &str) -> Option<Command> {
171294 None
172295}
173296
174- /// Generates the complete `dlltool` executable invocation command.
175- ///
176- /// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors.
177- fn build_dlltool_command (
178- mut command : Command ,
179- dlltool : & str ,
180- arch : & str ,
181- defpath : & Path ,
182- out_dir : & Path ,
183- ) -> Command {
184- let mut libpath = out_dir. to_owned ( ) ;
185-
186- // Check whether we are using LLVM `dlltool` or MinGW `dlltool`.
187- if dlltool == DLLTOOL_MSVC {
188- libpath. push ( IMPLIB_FILE_MSVC ) ;
189-
190- // LLVM tools use their own target architecture names...
191- let machine = match arch {
192- "x86_64" => "i386:x86-64" ,
193- "x86" => "i386" ,
194- "aarch64" => "arm64" ,
195- _ => arch,
196- } ;
197-
198- command
199- . arg ( "-m" )
200- . arg ( machine)
201- . arg ( "-d" )
202- . arg ( defpath)
203- . arg ( "-l" )
204- . arg ( libpath) ;
205- } else if dlltool == LIB_MSVC {
206- libpath. push ( IMPLIB_FILE_MSVC ) ;
207-
208- // lib.exe use their own target architecure names...
209- let machine = match arch {
210- "x86_64" => "X64" ,
211- "x86" => "X86" ,
212- "aarch64" => "ARM64" ,
213- _ => arch,
214- } ;
215- command
216- . arg ( format ! ( "/MACHINE:{}" , machine) )
217- . arg ( format ! ( "/DEF:{}" , defpath. display( ) ) )
218- . arg ( format ! ( "/OUT:{}" , libpath. display( ) ) ) ;
219- } else {
220- libpath. push ( IMPLIB_FILE_GNU ) ;
221-
222- command
223- . arg ( "--input-def" )
224- . arg ( defpath)
225- . arg ( "--output-lib" )
226- . arg ( libpath) ;
227- }
228-
229- command
230- }
231-
232297#[ cfg( test) ]
233298mod tests {
234299 use std:: path:: PathBuf ;
@@ -244,7 +309,12 @@ mod tests {
244309 dir. push ( "x86_64-pc-windows-gnu" ) ;
245310 dir. push ( "python3-dll" ) ;
246311
247- generate_implib_for_target ( & 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+ }
248318 }
249319
250320 #[ cfg( unix) ]
@@ -266,6 +336,12 @@ mod tests {
266336 dir. push ( "python3-dll" ) ;
267337
268338 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+ }
269345 }
270346
271347 #[ test]
0 commit comments