python3_dll_a/lib.rs
1//! Standalone `python3(y).dll` import library generator
2//! ====================================================
3//!
4//! Generates import libraries for the Python DLL
5//! (either `python3.dll` or `python3y.dll`)
6//! for MinGW-w64 and MSVC (cross-)compile targets.
7//!
8//! This crate **does not require** Python 3 distribution files
9//! to be present on the (cross-)compile host system.
10//!
11//! This crate uses the binutils `dlltool` program to generate
12//! the Python DLL import libraries for MinGW-w64 targets.
13//! Setting `PYO3_MINGW_DLLTOOL` environment variable overrides
14//! the default `dlltool` command name for the target.
15//!
16//! **Note:** MSVC cross-compile targets require either LLVM binutils
17//! or Zig to be available on the host system.
18//! More specifically, `python3-dll-a` requires `llvm-dlltool` executable
19//! to be present in `PATH` when targeting `*-pc-windows-msvc` from Linux.
20//!
21//! Alternatively, `ZIG_COMMAND` environment variable may be set to e.g. `"zig"`
22//! or `"python -m ziglang"`, then `zig dlltool` will be used in place
23//! of `llvm-dlltool` (or MinGW binutils).
24//!
25//! PyO3 integration
26//! ----------------
27//!
28//! Since version **0.16.5**, the `pyo3` crate implements support
29//! for both the Stable ABI and version-specific Python DLL import
30//! library generation via its new `generate-import-lib` feature.
31//!
32//! In this configuration, `python3-dll-a` becomes a `pyo3` crate dependency
33//! and is automatically invoked by its build script in both native
34//! and cross compilation scenarios.
35//!
36//! ### Example `Cargo.toml` usage for an `abi3` PyO3 extension module
37//!
38//! ```toml
39//! [dependencies]
40//! pyo3 = { version = "0.16.5", features = ["extension-module", "abi3-py37", "generate-import-lib"] }
41//! ```
42//!
43//! ### Example `Cargo.toml` usage for a standard PyO3 extension module
44//!
45//! ```toml
46//! [dependencies]
47//! pyo3 = { version = "0.16.5", features = ["extension-module", "generate-import-lib"] }
48//! ```
49//!
50//! Standalone build script usage
51//! -----------------------------
52//!
53//! If an older `pyo3` crate version is used, or a different Python bindings
54//! library is required, `python3-dll-a` can be used directly
55//! from the crate build script.
56//!
57//! The examples below assume using an older version of PyO3.
58//!
59//! ### Example `build.rs` script for an `abi3` PyO3 extension
60//!
61//! The following cargo build script can be used to cross-compile Stable ABI
62//! PyO3 extension modules for Windows (64/32-bit x86 or 64-bit ARM)
63//! using either MinGW-w64 or MSVC target environment ABI:
64//!
65//! ```no_run
66//! fn main() {
67//! if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
68//! let cross_lib_dir = std::env::var_os("PYO3_CROSS_LIB_DIR")
69//! .expect("PYO3_CROSS_LIB_DIR is not set when cross-compiling");
70//! let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
71//! let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
72//!
73//! let libdir = std::path::Path::new(&cross_lib_dir);
74//! python3_dll_a::generate_implib_for_target(libdir, &arch, &env)
75//! .expect("python3.dll import library generator failed");
76//! }
77//! }
78//! ```
79//!
80//! A compatible `python3.dll` import library file named `python3.dll.a`
81//! or `python3.lib` will be automatically created in the directory
82//! pointed by the `PYO3_CROSS_LIB_DIR` environment variable.
83//!
84//! ### Example `cargo build` invocation
85//!
86//! ```sh
87//! PYO3_CROSS_LIB_DIR=target/python3-dll cargo build --target x86_64-pc-windows-gnu
88//! ```
89//!
90//! Generating version-specific `python3y.dll` import libraries
91//! -----------------------------------------------------------
92//!
93//! As an advanced feature, `python3-dll-a` can generate Python version
94//! specific import libraries such as `python39.lib` or `python313t.lib`.
95//!
96//! See the [`ImportLibraryGenerator`] builder API description for details.
97
98#![deny(missing_docs)]
99#![allow(clippy::needless_doctest_main)]
100#![allow(clippy::uninlined_format_args)]
101
102use std::env;
103use std::fs::{create_dir_all, write};
104use std::io::{Error, ErrorKind, Result};
105use std::path::{Path, PathBuf};
106use std::process::Command;
107
108/// Import library file extension for the GNU environment ABI (MinGW-w64)
109const IMPLIB_EXT_GNU: &str = ".dll.a";
110
111/// Import library file extension for the MSVC environment ABI
112const IMPLIB_EXT_MSVC: &str = ".lib";
113
114/// Canonical MinGW-w64 `dlltool` program name
115const DLLTOOL_GNU: &str = "x86_64-w64-mingw32-dlltool";
116
117/// Canonical MinGW-w64 `dlltool` program name (32-bit version)
118const DLLTOOL_GNU_32: &str = "i686-w64-mingw32-dlltool";
119
120/// Canonical `dlltool` program name for the MSVC environment ABI (LLVM dlltool)
121const DLLTOOL_MSVC: &str = "llvm-dlltool";
122
123/// Canonical `lib` program name for the MSVC environment ABI (MSVC lib.exe)
124#[cfg(windows)]
125const LIB_MSVC: &str = "lib.exe";
126
127/// Python interpreter implementations
128#[derive(Debug, Clone, Copy)]
129pub enum PythonImplementation {
130 /// CPython
131 CPython,
132 /// PyPy
133 PyPy,
134}
135
136/// Windows import library generator for Python
137///
138/// Generates `python3.dll` or `pythonXY.dll` import library directly from the
139/// embedded Python ABI definitions data for the specified compile target.
140///
141/// ABI-tagged versioned Python DLLs such as `python313t.dll` are also supported
142/// via an optional ABI flags string parameter.
143///
144/// Example usage
145/// -------------
146///
147/// ```no_run
148/// # use std::path::Path;
149/// # use python3_dll_a::ImportLibraryGenerator;
150/// // Generate `python3.dll.a` in "target/python3-dll-a"
151/// ImportLibraryGenerator::new("x86_64", "gnu")
152/// .generate(Path::new("target/python3-dll-a"))
153/// .unwrap();
154///
155/// // Generate `python3.lib` in "target/python3-lib"
156/// ImportLibraryGenerator::new("x86_64", "msvc")
157/// .generate(Path::new("target/python3-lib"))
158/// .unwrap();
159///
160/// // Generate `python39.dll.a` in "target/python3-dll-a"
161/// ImportLibraryGenerator::new("x86_64", "gnu")
162/// .version(Some((3, 9)))
163/// .generate(Path::new("target/python3-dll-a"))
164/// .unwrap();
165///
166/// // Generate `python38.lib` in "target/python3-lib"
167/// ImportLibraryGenerator::new("x86_64", "msvc")
168/// .version(Some((3, 8)))
169/// .generate(Path::new("target/python3-lib"))
170/// .unwrap();
171///
172/// // Generate `python313t.lib` in "target/python3-lib"
173/// ImportLibraryGenerator::new("x86_64", "msvc")
174/// .version(Some((3, 13)))
175/// .abiflags(Some("t"))
176/// .generate(Path::new("target/python3-lib"))
177/// .unwrap();
178/// ```
179#[derive(Debug, Clone)]
180pub struct ImportLibraryGenerator {
181 /// The compile target architecture name (as in `CARGO_CFG_TARGET_ARCH`)
182 arch: String,
183 // The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
184 env: String,
185 /// Major and minor Python version (for `pythonXY.dll` only)
186 version: Option<(u8, u8)>,
187 /// Python interpreter implementation
188 implementation: PythonImplementation,
189 /// Optional Python ABI flags
190 ///
191 /// For example, `"t"` stands for the free-threaded CPython v3.13 build
192 /// aka CPython `3.13t`.
193 abiflags: Option<String>,
194}
195
196impl ImportLibraryGenerator {
197 /// Creates a new import library generator for the specified compile target.
198 ///
199 /// The compile target architecture name (as in `CARGO_CFG_TARGET_ARCH`)
200 /// is passed in `arch`.
201 ///
202 /// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
203 /// is passed in `env`.
204 #[must_use]
205 pub fn new(arch: &str, env: &str) -> Self {
206 ImportLibraryGenerator {
207 arch: arch.to_string(),
208 env: env.to_string(),
209 version: None,
210 implementation: PythonImplementation::CPython,
211 abiflags: None,
212 }
213 }
214
215 /// Sets major and minor version for the `pythonXY.dll` import library.
216 ///
217 /// The version-agnostic `python3.dll` is generated by default.
218 pub fn version(&mut self, version: Option<(u8, u8)>) -> &mut Self {
219 self.version = version;
220 self
221 }
222
223 /// Sets the ABI flags for the `pythonXY<abi>.dll` import library.
224 ///
225 /// For example, `"t"` stands for the free-threaded CPython v3.13 build
226 /// aka CPython `3.13t`.
227 /// In this case, `python313t.dll` import library will be generated.
228 ///
229 /// The untagged versioned `pythonXY.dll` import library
230 /// is generated by default.
231 pub fn abiflags(&mut self, flags: Option<&str>) -> &mut Self {
232 self.abiflags = flags.map(ToOwned::to_owned);
233 self
234 }
235
236 /// Sets Python interpreter implementation
237 pub fn implementation(&mut self, implementation: PythonImplementation) -> &mut Self {
238 self.implementation = implementation;
239 self
240 }
241
242 /// Generates the Python DLL import library in `out_dir`.
243 ///
244 /// The version-agnostic `python3.dll` import library is generated
245 /// by default unless the version-specific `pythonXY.dll` import
246 /// was requested via `version()`.
247 pub fn generate(&self, out_dir: &Path) -> Result<()> {
248 create_dir_all(out_dir)?;
249
250 let defpath = self.write_def_file(out_dir)?;
251
252 // Try to guess the `dlltool` executable name from the target triple.
253 let dlltool_command = DllToolCommand::find_for_target(&self.arch, &self.env)?;
254
255 // Get the import library file extension from the used `dlltool` flavor.
256 let implib_ext = dlltool_command.implib_file_ext();
257
258 let implib_file = self.implib_file_path(out_dir, implib_ext);
259
260 // Build the complete `dlltool` command with all required arguments.
261 let mut command = dlltool_command.build(&defpath, &implib_file);
262
263 // Run the selected `dlltool` executable to generate the import library.
264 let status = command.status().map_err(|e| {
265 let msg = format!("{:?} failed with {}", command, e);
266 Error::new(e.kind(), msg)
267 })?;
268
269 if status.success() {
270 Ok(())
271 } else {
272 let msg = format!("{:?} failed with {}", command, status);
273 Err(Error::new(ErrorKind::Other, msg))
274 }
275 }
276
277 /// Writes out the embedded Python library definitions file to `out_dir`.
278 ///
279 /// Returns the newly created `python3.def` or `pythonXY.def` file path.
280 fn write_def_file(&self, out_dir: &Path) -> Result<PathBuf> {
281 let (def_file, def_file_content) = match self.implementation {
282 PythonImplementation::CPython => match self.version {
283 None => ("python3.def", include_str!("python3.def")),
284 Some((3, 7)) => ("python37.def", include_str!("python37.def")),
285 Some((3, 8)) => ("python38.def", include_str!("python38.def")),
286 Some((3, 9)) => ("python39.def", include_str!("python39.def")),
287 Some((3, 10)) => ("python310.def", include_str!("python310.def")),
288 Some((3, 11)) => ("python311.def", include_str!("python311.def")),
289 Some((3, 12)) => ("python312.def", include_str!("python312.def")),
290 Some((3, 13)) => match self.abiflags.as_deref() {
291 Some("t") => ("python313t.def", include_str!("python313t.def")),
292 None => ("python313.def", include_str!("python313.def")),
293 _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python ABI flags")),
294 },
295 _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python version")),
296 },
297 PythonImplementation::PyPy => match self.version {
298 Some((3, 7)) | Some((3, 8)) => ("libpypy3-c.def", include_str!("libpypy3-c.def")),
299 Some((3, 9)) => ("libpypy3.9-c.def", include_str!("libpypy3.9-c.def")),
300 Some((3, 10)) => ("libpypy3.10-c.def", include_str!("libpypy3.10-c.def")),
301 Some((3, 11)) => ("libpypy3.11-c.def", include_str!("libpypy3.11-c.def")),
302 _ => return Err(Error::new(ErrorKind::Other, "Unsupported PyPy version")),
303 },
304 };
305
306 let mut defpath = out_dir.to_owned();
307 defpath.push(def_file);
308
309 write(&defpath, def_file_content)?;
310
311 Ok(defpath)
312 }
313
314 /// Builds the generated import library file name.
315 ///
316 /// The output file extension is passed in `libext`.
317 ///
318 /// Returns the full import library file path under `out_dir`.
319 fn implib_file_path(&self, out_dir: &Path, libext: &str) -> PathBuf {
320 let abiflags = self.abiflags.as_deref().unwrap_or_default();
321 let libname = match self.version {
322 Some((major, minor)) => {
323 format!("python{}{}{}{}", major, minor, abiflags, libext)
324 }
325 None => format!("python3{}", libext),
326 };
327
328 let mut libpath = out_dir.to_owned();
329 libpath.push(libname);
330
331 libpath
332 }
333}
334
335/// Generates `python3.dll` import library directly from the embedded
336/// Python Stable ABI definitions data for the specified compile target.
337///
338/// The import library file named `python3.dll.a` or `python3.lib` is created
339/// in directory `out_dir`.
340///
341/// The compile target architecture name (as in `CARGO_CFG_TARGET_ARCH`)
342/// is passed in `arch`.
343///
344/// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
345/// is passed in `env`.
346pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Result<()> {
347 ImportLibraryGenerator::new(arch, env).generate(out_dir)
348}
349
350/// `dlltool` utility command builder
351///
352/// Supports Visual Studio `lib.exe`, MinGW, LLVM and Zig `dlltool` flavors.
353#[derive(Debug)]
354enum DllToolCommand {
355 /// MinGW `dlltool` program (with prefix)
356 Mingw { command: Command },
357 /// LLVM `llvm-dlltool` program (no prefix)
358 Llvm { command: Command, machine: String },
359 /// MSVC `lib.exe` program (no prefix)
360 LibExe { command: Command, machine: String },
361 /// `zig dlltool` wrapper (no prefix)
362 Zig { command: Command, machine: String },
363}
364
365impl DllToolCommand {
366 /// Attempts to find the best matching `dlltool` flavor for the target.
367 fn find_for_target(arch: &str, env: &str) -> Result<DllToolCommand> {
368 // LLVM tools use their own target architecture names...
369 let machine = match arch {
370 "x86_64" => "i386:x86-64",
371 "x86" => "i386",
372 "aarch64" => "arm64",
373 arch => arch,
374 }
375 .to_owned();
376
377 // If `zig cc` is used as the linker, `zig dlltool` is the best choice.
378 if let Some(command) = find_zig() {
379 return Ok(DllToolCommand::Zig { command, machine });
380 }
381
382 match env {
383 // 64-bit and 32-bit MinGW-w64 (aka `{x86_64,i686}-pc-windows-gnu`)
384 "gnu" => Ok(DllToolCommand::Mingw {
385 command: get_mingw_dlltool(arch)?,
386 }),
387
388 // MSVC ABI (multiarch)
389 "msvc" => {
390 if let Some(command) = find_lib_exe(arch) {
391 // MSVC tools use their own target architecture names...
392 let machine = match arch {
393 "x86_64" => "X64",
394 "x86" => "X86",
395 "aarch64" => "ARM64",
396 arch => arch,
397 }
398 .to_owned();
399
400 Ok(DllToolCommand::LibExe { command, machine })
401 } else {
402 let command = Command::new(DLLTOOL_MSVC);
403
404 Ok(DllToolCommand::Llvm { command, machine })
405 }
406 }
407 _ => {
408 let msg = format!("Unsupported target env ABI '{}'", env);
409 Err(Error::new(ErrorKind::Other, msg))
410 }
411 }
412 }
413
414 /// Returns the import library file extension used by
415 /// this `dlltool` flavor.
416 fn implib_file_ext(&self) -> &'static str {
417 if let DllToolCommand::Mingw { .. } = self {
418 IMPLIB_EXT_GNU
419 } else {
420 IMPLIB_EXT_MSVC
421 }
422 }
423
424 /// Generates the complete `dlltool` executable invocation command.
425 fn build(self, defpath: &Path, libpath: &Path) -> Command {
426 match self {
427 Self::Mingw { mut command } => {
428 command
429 .arg("--input-def")
430 .arg(defpath)
431 .arg("--output-lib")
432 .arg(libpath);
433
434 command
435 }
436 Self::Llvm {
437 mut command,
438 machine,
439 } => {
440 command
441 .arg("-m")
442 .arg(machine)
443 .arg("-d")
444 .arg(defpath)
445 .arg("-l")
446 .arg(libpath);
447
448 command
449 }
450 Self::LibExe {
451 mut command,
452 machine,
453 } => {
454 command
455 .arg(format!("/MACHINE:{}", machine))
456 .arg(format!("/DEF:{}", defpath.display()))
457 .arg(format!("/OUT:{}", libpath.display()));
458
459 command
460 }
461 Self::Zig {
462 mut command,
463 machine,
464 } => {
465 // Same as `llvm-dlltool`, but invoked as `zig dlltool`.
466 command
467 .arg("dlltool")
468 .arg("-m")
469 .arg(machine)
470 .arg("-d")
471 .arg(defpath)
472 .arg("-l")
473 .arg(libpath);
474
475 command
476 }
477 }
478 }
479}
480
481/// Chooses the appropriate MinGW-w64 `dlltool` executable
482/// for the target architecture.
483///
484/// Examines the user-provided `PYO3_MINGW_DLLTOOL` environment variable first
485/// and falls back to the default MinGW-w64 arch prefixes.
486fn get_mingw_dlltool(arch: &str) -> Result<Command> {
487 if let Ok(user_dlltool) = env::var("PYO3_MINGW_DLLTOOL") {
488 Ok(Command::new(user_dlltool))
489 } else {
490 let prefix_dlltool = match arch {
491 // 64-bit MinGW-w64 (aka `x86_64-pc-windows-gnu`)
492 "x86_64" => Ok(DLLTOOL_GNU),
493 // 32-bit MinGW-w64 (aka `i686-pc-windows-gnu`)
494 "x86" => Ok(DLLTOOL_GNU_32),
495 // AArch64?
496 _ => {
497 let msg = format!("Unsupported MinGW target arch '{}'", arch);
498 Err(Error::new(ErrorKind::Other, msg))
499 }
500 }?;
501
502 Ok(Command::new(prefix_dlltool))
503 }
504}
505
506/// Finds the `zig` executable (when built by `maturin --zig`).
507///
508/// Examines the `ZIG_COMMAND` environment variable
509/// to find out if `zig cc` is being used as the linker.
510fn find_zig() -> Option<Command> {
511 // `ZIG_COMMAND` may contain simply `zig` or `/usr/bin/zig`,
512 // or a more complex construct like `python3 -m ziglang`.
513 let zig_command = env::var("ZIG_COMMAND").ok()?;
514
515 // Try to emulate `sh -c ${ZIG_COMMAND}`.
516 let mut zig_cmdlet = zig_command.split_ascii_whitespace();
517
518 // Extract the main program component (e.g. `zig` or `python3`).
519 let mut zig = Command::new(zig_cmdlet.next()?);
520
521 // Append the rest of the commandlet.
522 zig.args(zig_cmdlet);
523
524 Some(zig)
525}
526
527/// Finds Visual Studio `lib.exe` when running on Windows.
528#[cfg(windows)]
529fn find_lib_exe(arch: &str) -> Option<Command> {
530 let target = match arch {
531 "x86_64" => "x86_64-pc-windows-msvc",
532 "x86" => "i686-pc-windows-msvc",
533 "aarch64" => "aarch64-pc-windows-msvc",
534 _ => return None,
535 };
536
537 cc::windows_registry::find(target, LIB_MSVC)
538}
539
540#[cfg(not(windows))]
541fn find_lib_exe(_arch: &str) -> Option<Command> {
542 None
543}
544
545#[cfg(test)]
546mod tests {
547 use std::path::PathBuf;
548
549 use super::*;
550
551 #[cfg(unix)]
552 #[test]
553 fn generate() {
554 // FIXME: Use "target/<arch>" dirs for temporary files.
555 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
556 dir.push("target");
557 dir.push("x86_64-pc-windows-gnu");
558 dir.push("python3-dll");
559
560 ImportLibraryGenerator::new("x86_64", "gnu")
561 .generate(&dir)
562 .unwrap();
563
564 for minor in 7..=13 {
565 ImportLibraryGenerator::new("x86_64", "gnu")
566 .version(Some((3, minor)))
567 .generate(&dir)
568 .unwrap();
569 }
570
571 // Free-threaded CPython v3.13+
572 for minor in 13..=13 {
573 ImportLibraryGenerator::new("x86_64", "gnu")
574 .version(Some((3, minor)))
575 .abiflags(Some("t"))
576 .generate(&dir)
577 .unwrap();
578 }
579
580 // PyPy
581 for minor in 7..=11 {
582 ImportLibraryGenerator::new("x86_64", "gnu")
583 .version(Some((3, minor)))
584 .implementation(PythonImplementation::PyPy)
585 .generate(&dir)
586 .unwrap();
587 }
588 }
589
590 #[cfg(unix)]
591 #[test]
592 fn generate_gnu32() {
593 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
594 dir.push("target");
595 dir.push("i686-pc-windows-gnu");
596 dir.push("python3-dll");
597
598 generate_implib_for_target(&dir, "x86", "gnu").unwrap();
599 }
600
601 #[test]
602 fn generate_msvc() {
603 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
604 dir.push("target");
605 dir.push("x86_64-pc-windows-msvc");
606 dir.push("python3-dll");
607
608 ImportLibraryGenerator::new("x86_64", "msvc")
609 .generate(&dir)
610 .unwrap();
611
612 for minor in 7..=13 {
613 ImportLibraryGenerator::new("x86_64", "msvc")
614 .version(Some((3, minor)))
615 .generate(&dir)
616 .unwrap();
617 }
618
619 // Free-threaded CPython v3.13+
620 for minor in 13..=13 {
621 ImportLibraryGenerator::new("x86_64", "msvc")
622 .version(Some((3, minor)))
623 .abiflags(Some("t"))
624 .generate(&dir)
625 .unwrap();
626 }
627
628 // PyPy
629 for minor in 7..=11 {
630 ImportLibraryGenerator::new("x86_64", "msvc")
631 .version(Some((3, minor)))
632 .implementation(PythonImplementation::PyPy)
633 .generate(&dir)
634 .unwrap();
635 }
636 }
637
638 #[test]
639 fn generate_msvc32() {
640 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
641 dir.push("target");
642 dir.push("i686-pc-windows-msvc");
643 dir.push("python3-dll");
644
645 generate_implib_for_target(&dir, "x86", "msvc").unwrap();
646 }
647
648 #[test]
649 fn generate_msvc_arm64() {
650 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
651 dir.push("target");
652 dir.push("aarch64-pc-windows-msvc");
653 dir.push("python3-dll");
654
655 ImportLibraryGenerator::new("aarch64", "msvc")
656 .generate(&dir)
657 .unwrap();
658
659 for minor in 7..=13 {
660 ImportLibraryGenerator::new("aarch64", "msvc")
661 .version(Some((3, minor)))
662 .generate(&dir)
663 .unwrap();
664 }
665
666 // Free-threaded CPython v3.13+
667 for minor in 13..=13 {
668 let mut generator = ImportLibraryGenerator::new("aarch64", "msvc");
669 generator.version(Some((3, minor))).abiflags(Some("t"));
670 let implib_file_path = generator.implib_file_path(&dir, IMPLIB_EXT_MSVC);
671 let implib_file_stem = implib_file_path.file_stem().unwrap().to_str().unwrap();
672 assert!(implib_file_stem.ends_with("t"));
673
674 generator.generate(&dir).unwrap();
675 }
676
677 // PyPy
678 for minor in 7..=11 {
679 ImportLibraryGenerator::new("aarch64", "msvc")
680 .version(Some((3, minor)))
681 .implementation(PythonImplementation::PyPy)
682 .generate(&dir)
683 .unwrap();
684 }
685 }
686}