Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate import libraries without Command #404

Merged
merged 3 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ anyhow = "1.0"
cc = "1.0"
glob = "0.3"
itertools = "0.13"
object = "0.36.4"
implib = "0.3.2"

# workaround cargo
[target.'cfg(windows)'.dependencies.windows-sys]
Expand Down
210 changes: 73 additions & 137 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read, Write};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
Expand All @@ -13,7 +13,9 @@ use cargo::util::interning::InternedString;
use cargo::{CliResult, GlobalContext};

use anyhow::Context as _;
use cargo_util::paths::{copy, create, create_dir_all, open, read, read_bytes, write};
use cargo_util::paths::{copy, create_dir_all, open, read, read_bytes, write};
use implib::def::ModuleDef;
use implib::{Flavor, ImportLibrary, MachineType};
use semver::Version;

use crate::build_targets::BuildTargets;
Expand Down Expand Up @@ -134,67 +136,35 @@ fn build_def_file(
target: &target::Target,
targetdir: &Path,
) -> anyhow::Result<()> {
let os = &target.os;
let env = &target.env;
if target.os == "windows" && target.env == "msvc" {
ws.gctx().shell().status("Building", ".def file")?;

if os == "windows" && env == "msvc" {
ws.gctx()
.shell()
.status("Building", ".def file using dumpbin")?;
// Parse the .dll as an object file
let dll_path = targetdir.join(format!("{}.dll", name.replace('-', "_")));
let dll_content = std::fs::read(&dll_path)?;
let dll_file = object::File::parse(&*dll_content)?;

let txt_path = targetdir.join(format!("{name}.txt"));
// Create the .def output file
let def_file = cargo_util::paths::create(targetdir.join(format!("{name}.def")))?;

let target_str = format!("{}-pc-windows-msvc", &target.arch);
let mut dumpbin = match cc::windows_registry::find(&target_str, "dumpbin.exe") {
Some(command) => command,
None => std::process::Command::new("dumpbin"),
};
write_def_file(dll_file, def_file)?;
}

dumpbin
.arg("/EXPORTS")
.arg(targetdir.join(format!("{}.dll", name.replace('-', "_"))));
dumpbin.arg(format!("/OUT:{}", txt_path.to_str().unwrap()));

let out = dumpbin.output()?;
if out.status.success() {
let txt_file = open(txt_path)?;
let buf_reader = BufReader::new(txt_file);
let mut def_file = create(targetdir.join(format!("{name}.def")))?;
writeln!(def_file, "EXPORTS")?;

// The Rust loop below is analogue to the following loop.
// for /f "skip=19 tokens=4" %A in (file.txt) do echo %A > file.def
// The most recent versions of dumpbin adds three lines of copyright
// information before the relevant content.
// If the "/OUT:file.txt" dumpbin's option is used, the three
// copyright lines are added to the shell, so the txt file
// contains three lines less.
// The Rust loop first skips 16 lines and then, for each line,
// deletes all the characters up to the fourth space included
// (skip=16 tokens=4)
for line in buf_reader
.lines()
.skip(16)
.take_while(|l| !l.as_ref().unwrap().is_empty())
.map(|l| {
l.unwrap()
.as_str()
.split_whitespace()
.nth(3)
.unwrap()
.to_string()
})
{
writeln!(def_file, "\t{line}")?;
}
Ok(())
}

Ok(())
} else {
Err(anyhow::anyhow!("Command failed {:?}", dumpbin))
}
} else {
Ok(())
fn write_def_file<W: std::io::Write>(dll_file: object::File, mut def_file: W) -> anyhow::Result<W> {
use object::read::Object;

writeln!(def_file, "EXPORTS")?;

for export in dll_file.exports()? {
def_file.write_all(b"\t")?;
def_file.write_all(export.name())?;
def_file.write_all(b"\n")?;
}

Ok(def_file)
}

/// Build import library for windows-gnu
Expand All @@ -203,75 +173,54 @@ fn build_implib_file(
name: &str,
target: &target::Target,
targetdir: &Path,
dlltool: &Path,
) -> anyhow::Result<()> {
let os = &target.os;
let env = &target.env;

if os == "windows" {
let arch = &target.arch;
if env == "gnu" {
ws.gctx()
.shell()
.status("Building", "implib using dlltool")?;

let binutils_arch = match arch.as_str() {
"x86_64" => "i386:x86-64",
"x86" => "i386",
"aarch64" => "arm64",
_ => unimplemented!("Windows support for {} is not implemented yet.", arch),
};
if target.os == "windows" {
ws.gctx().shell().status("Building", "implib")?;

let mut dlltool_command =
std::process::Command::new(dlltool.to_str().unwrap_or("dlltool"));
dlltool_command.arg("-m").arg(binutils_arch);
dlltool_command.arg("-D").arg(format!("{name}.dll"));
dlltool_command
.arg("-l")
.arg(targetdir.join(format!("{name}.dll.a")));
dlltool_command
.arg("-d")
.arg(targetdir.join(format!("{name}.def")));

let out = dlltool_command.output()?;
if out.status.success() {
Ok(())
} else {
Err(anyhow::anyhow!("Command failed {:?}", dlltool_command))
}
} else {
ws.gctx().shell().status("Building", "implib using lib")?;
let target_str = format!("{}-pc-windows-msvc", &target.arch);
let mut lib = match cc::windows_registry::find(&target_str, "lib.exe") {
Some(command) => command,
None => std::process::Command::new("lib"),
};
let lib_arch = match arch.as_str() {
"x86_64" => "X64",
"x86" => "IX86",
_ => unimplemented!("Windows support for {} is not implemented yet.", arch),
};
lib.arg(format!(
"/DEF:{}",
targetdir.join(format!("{name}.def")).display()
));
lib.arg(format!("/MACHINE:{lib_arch}"));
lib.arg(format!("/NAME:{name}.dll"));
lib.arg(format!(
"/OUT:{}",
targetdir.join(format!("{name}.dll.lib")).display()
));

let out = lib.output()?;
if out.status.success() {
Ok(())
} else {
Err(anyhow::anyhow!("Command failed {:?}", lib))
let def_path = targetdir.join(format!("{name}.def"));
let def_contents = cargo_util::paths::read(&def_path)?;

let flavor = match target.env.as_str() {
"msvc" => Flavor::Msvc,
_ => Flavor::Gnu,
};

let machine_type = match target.arch.as_str() {
"x86_64" => MachineType::AMD64,
"x86" => MachineType::I386,
"aarch64" => MachineType::ARM64,
_ => {
return Err(anyhow::anyhow!(
"Windows support for {} is not implemented yet.",
target.arch
))
}
}
} else {
Ok(())
};

let lib_path = match flavor {
Flavor::Msvc => targetdir.join(format!("{name}.dll.lib")),
Flavor::Gnu => targetdir.join(format!("{name}.dll.a")),
};

let lib_file = cargo_util::paths::create(lib_path)?;
write_implib(lib_file, machine_type, flavor, &def_contents)?;
}

Ok(())
}

fn write_implib<W: std::io::Write + std::io::Seek>(
mut w: W,
machine_type: MachineType,
flavor: Flavor,
def_contents: &str,
) -> anyhow::Result<W> {
let module_def = ModuleDef::parse(def_contents, machine_type)?;
let import_library = ImportLibrary::from_def(module_def, machine_type, flavor);

import_library.write_to(&mut w)?;

Ok(w)
}

#[derive(Debug)]
Expand Down Expand Up @@ -932,7 +881,7 @@ fn compile_with_exec(
let mut leaf_args: Vec<String> = rustc_target
.shared_object_link_args(&capi_config, &install_paths.libdir, root_output)
.into_iter()
.flat_map(|l| vec!["-C".to_string(), format!("link-arg={l}")])
.flat_map(|l| ["-C".to_string(), format!("link-arg={l}")])
.collect();

leaf_args.extend(pkg_rustflags.clone());
Expand Down Expand Up @@ -1175,20 +1124,7 @@ pub fn cbuild(
if !only_staticlib && capi_config.library.import_library {
let lib_name = name;
build_def_file(ws, lib_name, &rustc_target, &root_output)?;

let mut dlltool = std::env::var_os("DLLTOOL")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("dlltool"));

// dlltool argument overwrites environment var
if args.contains_id("dlltool") {
dlltool = args
.get_one::<PathBuf>("dlltool")
.map(PathBuf::from)
.unwrap();
}

build_implib_file(ws, lib_name, &rustc_target, &root_output, &dlltool)?;
build_implib_file(ws, lib_name, &rustc_target, &root_output)?;
}

if capi_config.header.enabled {
Expand Down