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}