Skip to content

Commit 9408f83

Browse files
committed
feat: Add ImportLibraryGenerator
1 parent 18d1ac8 commit 9408f83

File tree

2 files changed

+184
-135
lines changed

2 files changed

+184
-135
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ fn main() {
5656
let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
5757

5858
let libdir = std::path::Path::new(&cross_lib_dir);
59-
python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env)
59+
python3_dll_a::generate_implib_for_target(libdir, &arch, &env)
6060
.expect("python3.dll import library generator failed");
6161
}
6262
}

src/lib.rs

Lines changed: 183 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
//! let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
5757
//!
5858
//! let libdir = std::path::Path::new(&cross_lib_dir);
59-
//! python3_dll_a::generate_implib_for_target(None, libdir, &arch, &env)
59+
//! python3_dll_a::generate_implib_for_target(libdir, &arch, &env)
6060
//! .expect("python3.dll import library generator failed");
6161
//! }
6262
//! }
@@ -75,6 +75,7 @@
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;
@@ -98,6 +99,169 @@ const DLLTOOL_MSVC: &str = "llvm-dlltool";
9899
/// Canonical `lib` program name for the MSVC environment ABI (MSVC lib.exe)
99100
const LIB_MSVC: &str = "lib.exe";
100101

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+
101265
/// Generates `python3.dll` import library directly from the embedded
102266
/// Python Stable ABI definitions data for the specified compile target.
103267
///
@@ -109,70 +273,8 @@ const LIB_MSVC: &str = "lib.exe";
109273
///
110274
/// The compile target environment ABI name (as in `CARGO_CFG_TARGET_ENV`)
111275
/// is passed in `env`.
112-
pub fn generate_implib_for_target(
113-
version: Option<(u8, u8)>,
114-
out_dir: &Path,
115-
arch: &str,
116-
env: &str,
117-
) -> Result<()> {
118-
create_dir_all(out_dir)?;
119-
120-
let mut defpath = out_dir.to_owned();
121-
let (def_file, def_file_content) = match version {
122-
None => ("python3.def", include_str!("python3.def")),
123-
Some((3, 7)) => ("python37.def", include_str!("python37.def")),
124-
Some((3, 8)) => ("python38.def", include_str!("python38.def")),
125-
Some((3, 9)) => ("python39.def", include_str!("python39.def")),
126-
Some((3, 10)) => ("python310.def", include_str!("python310.def")),
127-
Some((3, 11)) => ("python311.def", include_str!("python311.def")),
128-
_ => return Err(Error::new(ErrorKind::Other, "Unsupported Python version")),
129-
};
130-
defpath.push(def_file);
131-
132-
write(&defpath, def_file_content)?;
133-
134-
// Try to guess the `dlltool` executable name from the target triple.
135-
let (command, dlltool, implib_file) = match (arch, env) {
136-
// 64-bit MinGW-w64 (aka x86_64-pc-windows-gnu)
137-
("x86_64", "gnu") => (
138-
Command::new(DLLTOOL_GNU),
139-
DLLTOOL_GNU,
140-
def_file.replace(".def", IMPLIB_EXT_GNU),
141-
),
142-
// 32-bit MinGW-w64 (aka i686-pc-windows-gnu)
143-
("x86", "gnu") => (
144-
Command::new(DLLTOOL_GNU_32),
145-
DLLTOOL_GNU_32,
146-
def_file.replace(".def", IMPLIB_EXT_GNU),
147-
),
148-
// MSVC ABI (multiarch)
149-
(_, "msvc") => {
150-
if let Some(command) = find_lib_exe(arch) {
151-
(command, LIB_MSVC, def_file.replace(".def", IMPLIB_EXT_MSVC))
152-
} else {
153-
(
154-
Command::new(DLLTOOL_MSVC),
155-
DLLTOOL_MSVC,
156-
def_file.replace(".def", IMPLIB_EXT_MSVC),
157-
)
158-
}
159-
}
160-
_ => {
161-
let msg = format!("Unsupported target arch '{arch}' or env ABI '{env}'");
162-
return Err(Error::new(ErrorKind::Other, msg));
163-
}
164-
};
165-
166-
// Run the selected `dlltool` executable to generate the import library.
167-
let status =
168-
build_dlltool_command(command, dlltool, &implib_file, arch, &defpath, out_dir).status()?;
169-
170-
if status.success() {
171-
Ok(())
172-
} else {
173-
let msg = format!("{dlltool} failed with {status}");
174-
Err(Error::new(ErrorKind::Other, msg))
175-
}
276+
pub fn generate_implib_for_target(out_dir: &Path, arch: &str, env: &str) -> Result<()> {
277+
ImportLibraryGenerator::new(arch, env).generate(out_dir)
176278
}
177279

178280
/// Find Visual Studio lib.exe on Windows
@@ -192,65 +294,6 @@ fn find_lib_exe(_arch: &str) -> Option<Command> {
192294
None
193295
}
194296

195-
/// Generates the complete `dlltool` executable invocation command.
196-
///
197-
/// Supports Visual Studio `lib.exe`, LLVM and MinGW `dlltool` flavors.
198-
fn build_dlltool_command(
199-
mut command: Command,
200-
dlltool: &str,
201-
implib_file: &str,
202-
arch: &str,
203-
defpath: &Path,
204-
out_dir: &Path,
205-
) -> Command {
206-
let mut libpath = out_dir.to_owned();
207-
208-
// Check whether we are using LLVM `dlltool` or MinGW `dlltool`.
209-
if dlltool == DLLTOOL_MSVC {
210-
libpath.push(implib_file);
211-
212-
// LLVM tools use their own target architecture names...
213-
let machine = match arch {
214-
"x86_64" => "i386:x86-64",
215-
"x86" => "i386",
216-
"aarch64" => "arm64",
217-
_ => arch,
218-
};
219-
220-
command
221-
.arg("-m")
222-
.arg(machine)
223-
.arg("-d")
224-
.arg(defpath)
225-
.arg("-l")
226-
.arg(libpath);
227-
} else if dlltool == LIB_MSVC {
228-
libpath.push(implib_file);
229-
230-
// lib.exe use their own target architecure names...
231-
let machine = match arch {
232-
"x86_64" => "X64",
233-
"x86" => "X86",
234-
"aarch64" => "ARM64",
235-
_ => arch,
236-
};
237-
command
238-
.arg(format!("/MACHINE:{}", machine))
239-
.arg(format!("/DEF:{}", defpath.display()))
240-
.arg(format!("/OUT:{}", libpath.display()));
241-
} else {
242-
libpath.push(implib_file);
243-
244-
command
245-
.arg("--input-def")
246-
.arg(defpath)
247-
.arg("--output-lib")
248-
.arg(libpath);
249-
}
250-
251-
command
252-
}
253-
254297
#[cfg(test)]
255298
mod tests {
256299
use std::path::PathBuf;
@@ -266,12 +309,12 @@ mod tests {
266309
dir.push("x86_64-pc-windows-gnu");
267310
dir.push("python3-dll");
268311

269-
generate_implib_for_target(None, &dir, "x86_64", "gnu").unwrap();
270-
generate_implib_for_target(Some((3, 7)), &dir, "x86_64", "gnu").unwrap();
271-
generate_implib_for_target(Some((3, 8)), &dir, "x86_64", "gnu").unwrap();
272-
generate_implib_for_target(Some((3, 9)), &dir, "x86_64", "gnu").unwrap();
273-
generate_implib_for_target(Some((3, 10)), &dir, "x86_64", "gnu").unwrap();
274-
generate_implib_for_target(Some((3, 11)), &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+
}
275318
}
276319

277320
#[cfg(unix)]
@@ -282,7 +325,7 @@ mod tests {
282325
dir.push("i686-pc-windows-gnu");
283326
dir.push("python3-dll");
284327

285-
generate_implib_for_target(None, &dir, "x86", "gnu").unwrap();
328+
generate_implib_for_target(&dir, "x86", "gnu").unwrap();
286329
}
287330

288331
#[test]
@@ -292,7 +335,13 @@ mod tests {
292335
dir.push("x86_64-pc-windows-msvc");
293336
dir.push("python3-dll");
294337

295-
generate_implib_for_target(None, &dir, "x86_64", "msvc").unwrap();
338+
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+
}
296345
}
297346

298347
#[test]
@@ -302,7 +351,7 @@ mod tests {
302351
dir.push("i686-pc-windows-msvc");
303352
dir.push("python3-dll");
304353

305-
generate_implib_for_target(None, &dir, "x86", "msvc").unwrap();
354+
generate_implib_for_target(&dir, "x86", "msvc").unwrap();
306355
}
307356

308357
#[test]
@@ -312,6 +361,6 @@ mod tests {
312361
dir.push("aarch64-pc-windows-msvc");
313362
dir.push("python3-dll");
314363

315-
generate_implib_for_target(None, &dir, "aarch64", "msvc").unwrap();
364+
generate_implib_for_target(&dir, "aarch64", "msvc").unwrap();
316365
}
317366
}

0 commit comments

Comments
 (0)