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 Some((3, 14)) => match self.abiflags.as_deref() {
296 Some("t") => ("python314t.def", include_str!("python314t.def")),
297 None => ("python314.def", include_str!("python314.def")),
298 _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python ABI flags")),
299 },
300 _ => return Err(Error::new(ErrorKind::Other, "Unsupported Python version")),
301 },
302 PythonImplementation::PyPy => match self.version {
303 Some((3, 7)) | Some((3, 8)) => ("libpypy3-c.def", include_str!("libpypy3-c.def")),
304 Some((3, 9)) => ("libpypy3.9-c.def", include_str!("libpypy3.9-c.def")),
305 Some((3, 10)) => ("libpypy3.10-c.def", include_str!("libpypy3.10-c.def")),
306 Some((3, 11)) => ("libpypy3.11-c.def", include_str!("libpypy3.11-c.def")),
307 _ => return Err(Error::new(ErrorKind::Other, "Unsupported PyPy version")),
308 },
309 };
310
311 let mut defpath = out_dir.to_owned();
312 defpath.push(def_file);
313
314 write(&defpath, def_file_content)?;
315
316 Ok(defpath)
317 }
318
319 /// Builds the generated import library file name.
320 ///
321 /// The output file extension is passed in `libext`.
322 ///
323 /// Returns the full import library file path under `out_dir`.
324 fn implib_file_path(&self, out_dir: &Path, libext: &str) -> PathBuf {
325 let abiflags = self.abiflags.as_deref().unwrap_or_default();
326 let libname = match self.version {
327 Some((major, minor)) => {
328 format!("python{}{}{}{}", major, minor, abiflags, libext)
329 }
330 None => format!("python3{}", libext),
331 };
332
333 let mut libpath = out_dir.to_owned();
334 libpath.push(libname);
335
336 libpath
337 }
338}
339
340/// Generates `python3.dll` import library directly from the embedded
341/// Python Stable ABI definitions data for the specified compile target.
342///
343/// The import library file named `python3.dll.a` or `python3.lib` is created
344/// in directory `out_dir`.
345///
346/// The compile target architecture name (as in `CARGO_CFG_TARGET_ARCH`)
347/// is passed in `arch`.
348///
349/// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
350/// is passed in `env`.
351pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Result<()> {
352 ImportLibraryGenerator::new(arch, env).generate(out_dir)
353}
354
355/// `dlltool` utility command builder
356///
357/// Supports Visual Studio `lib.exe`, MinGW, LLVM and Zig `dlltool` flavors.
358#[derive(Debug)]
359enum DllToolCommand {
360 /// MinGW `dlltool` program (with prefix)
361 Mingw { command: Command },
362 /// LLVM `llvm-dlltool` program (no prefix)
363 Llvm { command: Command, machine: String },
364 /// MSVC `lib.exe` program (no prefix)
365 LibExe { command: Command, machine: String },
366 /// `zig dlltool` wrapper (no prefix)
367 Zig { command: Command, machine: String },
368}
369
370impl DllToolCommand {
371 /// Attempts to find the best matching `dlltool` flavor for the target.
372 fn find_for_target(arch: &str, env: &str) -> Result<DllToolCommand> {
373 // LLVM tools use their own target architecture names...
374 let machine = match arch {
375 "x86_64" => "i386:x86-64",
376 "x86" => "i386",
377 "aarch64" => "arm64",
378 arch => arch,
379 }
380 .to_owned();
381
382 // If `zig cc` is used as the linker, `zig dlltool` is the best choice.
383 if let Some(command) = find_zig() {
384 return Ok(DllToolCommand::Zig { command, machine });
385 }
386
387 match env {
388 // 64-bit and 32-bit MinGW-w64 (aka `{x86_64,i686}-pc-windows-gnu`)
389 "gnu" => Ok(DllToolCommand::Mingw {
390 command: get_mingw_dlltool(arch)?,
391 }),
392
393 // MSVC ABI (multiarch)
394 "msvc" => {
395 if let Some(command) = find_lib_exe(arch) {
396 // MSVC tools use their own target architecture names...
397 let machine = match arch {
398 "x86_64" => "X64",
399 "x86" => "X86",
400 "aarch64" => "ARM64",
401 arch => arch,
402 }
403 .to_owned();
404
405 Ok(DllToolCommand::LibExe { command, machine })
406 } else {
407 let command = Command::new(DLLTOOL_MSVC);
408
409 Ok(DllToolCommand::Llvm { command, machine })
410 }
411 }
412 _ => {
413 let msg = format!("Unsupported target env ABI '{}'", env);
414 Err(Error::new(ErrorKind::Other, msg))
415 }
416 }
417 }
418
419 /// Returns the import library file extension used by
420 /// this `dlltool` flavor.
421 fn implib_file_ext(&self) -> &'static str {
422 if let DllToolCommand::Mingw { .. } = self {
423 IMPLIB_EXT_GNU
424 } else {
425 IMPLIB_EXT_MSVC
426 }
427 }
428
429 /// Generates the complete `dlltool` executable invocation command.
430 fn build(self, defpath: &Path, libpath: &Path) -> Command {
431 match self {
432 Self::Mingw { mut command } => {
433 command
434 .arg("--input-def")
435 .arg(defpath)
436 .arg("--output-lib")
437 .arg(libpath);
438
439 command
440 }
441 Self::Llvm {
442 mut command,
443 machine,
444 } => {
445 command
446 .arg("-m")
447 .arg(machine)
448 .arg("-d")
449 .arg(defpath)
450 .arg("-l")
451 .arg(libpath);
452
453 command
454 }
455 Self::LibExe {
456 mut command,
457 machine,
458 } => {
459 command
460 .arg(format!("/MACHINE:{}", machine))
461 .arg(format!("/DEF:{}", defpath.display()))
462 .arg(format!("/OUT:{}", libpath.display()));
463
464 command
465 }
466 Self::Zig {
467 mut command,
468 machine,
469 } => {
470 // Same as `llvm-dlltool`, but invoked as `zig dlltool`.
471 command
472 .arg("dlltool")
473 .arg("-m")
474 .arg(machine)
475 .arg("-d")
476 .arg(defpath)
477 .arg("-l")
478 .arg(libpath);
479
480 command
481 }
482 }
483 }
484}
485
486/// Chooses the appropriate MinGW-w64 `dlltool` executable
487/// for the target architecture.
488///
489/// Examines the user-provided `PYO3_MINGW_DLLTOOL` environment variable first
490/// and falls back to the default MinGW-w64 arch prefixes.
491fn get_mingw_dlltool(arch: &str) -> Result<Command> {
492 if let Ok(user_dlltool) = env::var("PYO3_MINGW_DLLTOOL") {
493 Ok(Command::new(user_dlltool))
494 } else {
495 let prefix_dlltool = match arch {
496 // 64-bit MinGW-w64 (aka `x86_64-pc-windows-gnu`)
497 "x86_64" => Ok(DLLTOOL_GNU),
498 // 32-bit MinGW-w64 (aka `i686-pc-windows-gnu`)
499 "x86" => Ok(DLLTOOL_GNU_32),
500 // AArch64?
501 _ => {
502 let msg = format!("Unsupported MinGW target arch '{}'", arch);
503 Err(Error::new(ErrorKind::Other, msg))
504 }
505 }?;
506
507 Ok(Command::new(prefix_dlltool))
508 }
509}
510
511/// Finds the `zig` executable (when built by `maturin --zig`).
512///
513/// Examines the `ZIG_COMMAND` environment variable
514/// to find out if `zig cc` is being used as the linker.
515fn find_zig() -> Option<Command> {
516 // `ZIG_COMMAND` may contain simply `zig` or `/usr/bin/zig`,
517 // or a more complex construct like `python3 -m ziglang`.
518 let zig_command = env::var("ZIG_COMMAND").ok()?;
519
520 // Try to emulate `sh -c ${ZIG_COMMAND}`.
521 let mut zig_cmdlet = zig_command.split_ascii_whitespace();
522
523 // Extract the main program component (e.g. `zig` or `python3`).
524 let mut zig = Command::new(zig_cmdlet.next()?);
525
526 // Append the rest of the commandlet.
527 zig.args(zig_cmdlet);
528
529 Some(zig)
530}
531
532/// Finds Visual Studio `lib.exe` when running on Windows.
533#[cfg(windows)]
534fn find_lib_exe(arch: &str) -> Option<Command> {
535 let target = match arch {
536 "x86_64" => "x86_64-pc-windows-msvc",
537 "x86" => "i686-pc-windows-msvc",
538 "aarch64" => "aarch64-pc-windows-msvc",
539 _ => return None,
540 };
541
542 cc::windows_registry::find(target, LIB_MSVC)
543}
544
545#[cfg(not(windows))]
546fn find_lib_exe(_arch: &str) -> Option<Command> {
547 None
548}
549
550#[cfg(test)]
551mod tests {
552 use std::path::PathBuf;
553
554 use super::*;
555
556 #[cfg(unix)]
557 #[test]
558 fn generate() {
559 // FIXME: Use "target/<arch>" dirs for temporary files.
560 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
561 dir.push("target");
562 dir.push("x86_64-pc-windows-gnu");
563 dir.push("python3-dll");
564
565 ImportLibraryGenerator::new("x86_64", "gnu")
566 .generate(&dir)
567 .unwrap();
568
569 for minor in 7..=14 {
570 ImportLibraryGenerator::new("x86_64", "gnu")
571 .version(Some((3, minor)))
572 .generate(&dir)
573 .unwrap();
574 }
575
576 // Free-threaded CPython v3.13+
577 for minor in 13..=14 {
578 ImportLibraryGenerator::new("x86_64", "gnu")
579 .version(Some((3, minor)))
580 .abiflags(Some("t"))
581 .generate(&dir)
582 .unwrap();
583 }
584
585 // PyPy
586 for minor in 7..=11 {
587 ImportLibraryGenerator::new("x86_64", "gnu")
588 .version(Some((3, minor)))
589 .implementation(PythonImplementation::PyPy)
590 .generate(&dir)
591 .unwrap();
592 }
593 }
594
595 #[cfg(unix)]
596 #[test]
597 fn generate_gnu32() {
598 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
599 dir.push("target");
600 dir.push("i686-pc-windows-gnu");
601 dir.push("python3-dll");
602
603 generate_implib_for_target(&dir, "x86", "gnu").unwrap();
604 }
605
606 #[test]
607 fn generate_msvc() {
608 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
609 dir.push("target");
610 dir.push("x86_64-pc-windows-msvc");
611 dir.push("python3-dll");
612
613 ImportLibraryGenerator::new("x86_64", "msvc")
614 .generate(&dir)
615 .unwrap();
616
617 for minor in 7..=14 {
618 ImportLibraryGenerator::new("x86_64", "msvc")
619 .version(Some((3, minor)))
620 .generate(&dir)
621 .unwrap();
622 }
623
624 // Free-threaded CPython v3.13+
625 for minor in 13..=14 {
626 ImportLibraryGenerator::new("x86_64", "msvc")
627 .version(Some((3, minor)))
628 .abiflags(Some("t"))
629 .generate(&dir)
630 .unwrap();
631 }
632
633 // PyPy
634 for minor in 7..=11 {
635 ImportLibraryGenerator::new("x86_64", "msvc")
636 .version(Some((3, minor)))
637 .implementation(PythonImplementation::PyPy)
638 .generate(&dir)
639 .unwrap();
640 }
641 }
642
643 #[test]
644 fn generate_msvc32() {
645 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
646 dir.push("target");
647 dir.push("i686-pc-windows-msvc");
648 dir.push("python3-dll");
649
650 generate_implib_for_target(&dir, "x86", "msvc").unwrap();
651 }
652
653 #[test]
654 fn generate_msvc_arm64() {
655 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
656 dir.push("target");
657 dir.push("aarch64-pc-windows-msvc");
658 dir.push("python3-dll");
659
660 ImportLibraryGenerator::new("aarch64", "msvc")
661 .generate(&dir)
662 .unwrap();
663
664 for minor in 7..=14 {
665 ImportLibraryGenerator::new("aarch64", "msvc")
666 .version(Some((3, minor)))
667 .generate(&dir)
668 .unwrap();
669 }
670
671 // Free-threaded CPython v3.13+
672 for minor in 13..=14 {
673 let mut generator = ImportLibraryGenerator::new("aarch64", "msvc");
674 generator.version(Some((3, minor))).abiflags(Some("t"));
675 let implib_file_path = generator.implib_file_path(&dir, IMPLIB_EXT_MSVC);
676 let implib_file_stem = implib_file_path.file_stem().unwrap().to_str().unwrap();
677 assert!(implib_file_stem.ends_with("t"));
678
679 generator.generate(&dir).unwrap();
680 }
681
682 // PyPy
683 for minor in 7..=11 {
684 ImportLibraryGenerator::new("aarch64", "msvc")
685 .version(Some((3, minor)))
686 .implementation(PythonImplementation::PyPy)
687 .generate(&dir)
688 .unwrap();
689 }
690 }
691}