Skip to content

Commit 7bf8962

Browse files
authored
Merge pull request #15 from messense/non-abi3
Add support for generating non-abi3 `pythonXY.dll`
2 parents d591bfc + 9408f83 commit 7bf8962

File tree

1 file changed

+181
-105
lines changed

1 file changed

+181
-105
lines changed

src/lib.rs

Lines changed: 181 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,17 @@
7575
#![deny(missing_docs)]
7676
#![allow(clippy::needless_doctest_main)]
7777

78+
use std::ffi::OsStr;
7879
use std::fs::{create_dir_all, write};
7980
use std::io::{Error, ErrorKind, Result};
8081
use std::path::Path;
8182
use 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
9691
const 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)
105100
const 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`.
118276
pub 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)]
233298
mod 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

Comments
 (0)