Skip to content

Commit c9e787e

Browse files
committed
Support merging archives on MacOS for desktop/iOS targets
We use libtool on Mac instead of the Rust `ar` library, and gobjcopy from `binutils` instead of objcopy.
1 parent 3a091dc commit c9e787e

File tree

10 files changed

+222
-38
lines changed

10 files changed

+222
-38
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
/local

Cargo.lock

+32-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "armerge"
3-
version = "1.0.0"
3+
version = "1.1.0"
44
authors = ["tux3 <[email protected]>"]
55
edition = "2018"
66
license = "MIT OR Apache-2.0"
@@ -17,3 +17,4 @@ rand = "0.7.3"
1717
object = "0.21.1"
1818
regex = "1.3.9"
1919
rayon = "1.4.0"
20+
which = "4.0.2"

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
You can use armerge to combine multiple static libraries into a single merged `ar` archive.
55
Optionally, it is possible to generate a static archive containing a single merged object file, where all non-public symbols are localized (hidden).
66

7-
This tool requires `ranlib`, `ld`, and `llvm-objcopy` installed on your host system. You may specify a different linker using the `LD` environment variable.
7+
This tool requires `ranlib`, `ld`, and `objcopy` installed on your host system.
8+
On macOS, `libtool`, `ld` and `gobjcopy` are used instead.
9+
You may specify a different linker using the `LD` environment variable.
810

911
```
1012
USAGE:

src/arbuilder.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::error::Error;
2+
use std::path::Path;
3+
4+
#[cfg(not(target_os = "macos"))]
5+
mod common;
6+
7+
#[cfg(target_os = "macos")]
8+
mod mac;
9+
10+
pub trait ArBuilder {
11+
fn append_obj<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Box<dyn Error>>;
12+
fn close(self) -> Result<(), Box<dyn Error>>;
13+
}
14+
15+
#[cfg(not(target_os = "macos"))]
16+
pub fn platform_builder(path: &Path, verbose: bool) -> impl ArBuilder {
17+
common::CommonArBuilder::new(path, verbose)
18+
}
19+
20+
#[cfg(target_os = "macos")]
21+
pub fn platform_builder(path: &Path, verbose: bool) -> impl ArBuilder {
22+
mac::MacArBuilder::new(path, verbose)
23+
}

src/arbuilder/common.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use crate::arbuilder::ArBuilder;
2+
use crate::archives;
3+
use ar::Builder;
4+
use std::error::Error;
5+
use std::fs::File;
6+
use std::path::{Path, PathBuf};
7+
8+
pub struct CommonArBuilder {
9+
builder: Builder<File>,
10+
output_path: PathBuf,
11+
closed: bool,
12+
}
13+
14+
impl ArBuilder for CommonArBuilder {
15+
fn append_obj<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Box<dyn Error>> {
16+
self.builder.append_path(path)?;
17+
Ok(())
18+
}
19+
20+
fn close(mut self) -> Result<(), Box<dyn Error>> {
21+
self.finalize_index()
22+
}
23+
}
24+
25+
impl CommonArBuilder {
26+
pub fn new(path: &Path, _verbose: bool) -> Self {
27+
Self {
28+
builder: Builder::new(File::create(path).expect("Failed to create output library")),
29+
output_path: path.to_owned(),
30+
closed: false,
31+
}
32+
}
33+
34+
fn finalize_index(&mut self) -> Result<(), Box<dyn Error>> {
35+
if self.closed {
36+
return Ok(());
37+
}
38+
self.closed = true;
39+
40+
archives::create_index(&self.output_path)
41+
}
42+
}
43+
44+
impl Drop for CommonArBuilder {
45+
fn drop(&mut self) {
46+
self.finalize_index().unwrap();
47+
}
48+
}

src/arbuilder/mac.rs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::arbuilder::ArBuilder;
2+
use std::error::Error;
3+
use std::ffi::OsString;
4+
use std::path::{Path, PathBuf};
5+
use std::process::Command;
6+
7+
pub struct MacArBuilder {
8+
output_path: PathBuf,
9+
obj_paths: Vec<PathBuf>,
10+
verbose: bool,
11+
closed: bool,
12+
}
13+
14+
impl ArBuilder for MacArBuilder {
15+
fn append_obj<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Box<dyn Error>> {
16+
self.obj_paths.push(path.as_ref().to_owned());
17+
Ok(())
18+
}
19+
20+
fn close(mut self) -> Result<(), Box<dyn Error>> {
21+
self.write_obj()
22+
}
23+
}
24+
25+
impl MacArBuilder {
26+
pub fn new(path: &Path, verbose: bool) -> Self {
27+
Self {
28+
output_path: path.to_owned(),
29+
obj_paths: vec![],
30+
verbose,
31+
closed: false,
32+
}
33+
}
34+
35+
fn write_obj(&mut self) -> Result<(), Box<dyn Error>> {
36+
if self.closed {
37+
return Ok(());
38+
}
39+
self.closed = true;
40+
41+
let mut args = [
42+
OsString::from("-static"),
43+
OsString::from("-o"),
44+
self.output_path.as_os_str().to_owned(),
45+
]
46+
.to_vec();
47+
let mut count = 0;
48+
args.extend(
49+
self.obj_paths
50+
.iter()
51+
.inspect(|_| count += 1)
52+
.map(|p| p.as_os_str().into()),
53+
);
54+
if self.verbose {
55+
println!("Merging {} objects", count);
56+
}
57+
58+
Command::new("libtool")
59+
.args(args)
60+
.status()
61+
.unwrap_or_else(|_| panic!("Failed to merged object files with `libtool`"));
62+
Ok(())
63+
}
64+
}
65+
66+
impl Drop for MacArBuilder {
67+
fn drop(&mut self) {
68+
self.write_obj().unwrap();
69+
}
70+
}

src/archives.rs

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
use crate::arbuilder::ArBuilder;
12
use crate::objects::ObjectTempDir;
2-
use ar::{Archive, Builder};
3+
use ar::Archive;
34
use rand::distributions::Alphanumeric;
45
use rand::{thread_rng, Rng};
56
use std::error::Error;
67
use std::fs::File;
7-
use std::io::Write;
8-
use std::path::{Path, PathBuf};
9-
use std::process::Command;
8+
use std::path::PathBuf;
109
use std::str::from_utf8;
1110
use tempdir::TempDir;
1211

@@ -32,7 +31,10 @@ pub fn extract_objects(archives: &[PathBuf]) -> Result<ObjectTempDir, Box<dyn Er
3231
Ok(ObjectTempDir { dir, objects })
3332
}
3433

35-
pub fn create_index(archive_path: &Path) -> Result<(), Box<dyn Error>> {
34+
#[cfg(not(target_os = "macos"))]
35+
pub fn create_index(archive_path: &std::path::Path) -> Result<(), Box<dyn Error>> {
36+
use std::process::Command;
37+
3638
Command::new("ranlib")
3739
.args(vec![archive_path])
3840
.status()
@@ -41,14 +43,13 @@ pub fn create_index(archive_path: &Path) -> Result<(), Box<dyn Error>> {
4143
Ok(())
4244
}
4345

44-
pub fn merge<T: Write>(mut output: Builder<T>, archives: &[PathBuf]) -> Result<(), Box<dyn Error>> {
45-
for archive_path in archives {
46-
let mut archive = Archive::new(File::open(archive_path)?);
47-
while let Some(entry_result) = archive.next_entry() {
48-
let mut entry = entry_result?;
49-
let header = entry.header().clone();
50-
output.append(&header, &mut entry)?;
51-
}
46+
pub fn merge(mut output: impl ArBuilder, archives: &[PathBuf]) -> Result<(), Box<dyn Error>> {
47+
let objects_dir = extract_objects(archives)?;
48+
49+
for obj_path in objects_dir.objects {
50+
output.append_obj(obj_path)?;
5251
}
52+
53+
output.close()?;
5354
Ok(())
5455
}

src/main.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
mod arbuilder;
12
mod archives;
23
mod object_syms;
34
mod objects;
45

5-
use ar::Builder;
66
use std::error::Error;
7-
use std::fs::File;
87
use std::path::PathBuf;
98
use structopt::StructOpt;
109

@@ -31,14 +30,13 @@ struct Opt {
3130
fn main() -> Result<(), Box<dyn Error>> {
3231
let opt = Opt::from_args();
3332

34-
let builder = Builder::new(File::create(&opt.output)?);
33+
let builder = arbuilder::platform_builder(&opt.output, opt.verbose);
3534
if opt.keep_symbols.is_empty() {
3635
archives::merge(builder, &opt.inputs)?;
3736
} else {
3837
let objects_dir = archives::extract_objects(&opt.inputs)?;
3938
objects::merge(builder, objects_dir, opt.keep_symbols, opt.verbose)?;
4039
}
41-
archives::create_index(&opt.output)?;
4240

4341
Ok(())
4442
}

src/objects.rs

+27-18
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
use crate::arbuilder::ArBuilder;
12
use crate::object_syms::ObjectSyms;
2-
use ar::{Builder, Header};
33
use object::{Object, SymbolKind};
44
use rayon::prelude::*;
55
use regex::Regex;
66
use std::collections::{HashMap, HashSet};
77
use std::error::Error;
88
use std::ffi::OsString;
9-
use std::fs::File;
109
use std::io::Write;
1110
use std::path::{Path, PathBuf};
1211
use std::process::Command;
@@ -168,30 +167,49 @@ fn create_filter_list(
168167
Ok(filter_path)
169168
}
170169

170+
fn find_objcopy() -> Result<String, Box<dyn Error>> {
171+
for objcopy_name in &["objcopy", "gobjcopy"] {
172+
if which::which(objcopy_name).is_ok() {
173+
return Ok(objcopy_name.to_string());
174+
}
175+
}
176+
177+
let brew_gobjcopy = "/usr/local/opt/binutils/bin/gobjcopy";
178+
if Path::new(brew_gobjcopy).exists() {
179+
return Ok(brew_gobjcopy.to_owned());
180+
}
181+
182+
Err(From::from("Unable to find `objcopy` or `gobjcopy` on your system, please install binutils and update your PATH"))
183+
}
184+
171185
fn filter_symbols(object_path: &Path, filter_list_path: &Path) -> Result<(), Box<dyn Error>> {
172186
let args = vec![
173187
OsString::from("--localize-symbols"),
174188
filter_list_path.as_os_str().to_owned(),
175189
object_path.as_os_str().to_owned(),
176190
];
177-
Command::new("llvm-objcopy")
191+
Command::new(find_objcopy()?)
178192
.args(args)
179193
.status()
180-
.expect("Failed to filter symbols with `llvm-objcopy`");
194+
.expect("Failed to filter symbols with objcopy");
181195

182196
Ok(())
183197
}
184198

185-
pub fn merge<T: Write>(
186-
mut output: Builder<T>,
199+
pub fn merge(
200+
mut output: impl ArBuilder,
187201
objects: ObjectTempDir,
188-
keep_regexes: Vec<String>,
202+
mut keep_regexes: Vec<String>,
189203
verbose: bool,
190204
) -> Result<(), Box<dyn Error>> {
191205
let merged_name = "merged.o";
192206
let mut merged_path = objects.dir.path().to_owned();
193207
merged_path.push(merged_name);
194208

209+
// When filtering symbols to keep just the public API visible,
210+
// we must make an exception for the personality routines (if linked statically)
211+
keep_regexes.push("_?__g.._personality_.*".into());
212+
195213
let keep_regexes = keep_regexes
196214
.into_iter()
197215
.map(|r| Regex::new(&r))
@@ -203,17 +221,8 @@ pub fn merge<T: Write>(
203221
let filter_path = create_filter_list(&merged_path, &keep_regexes, verbose)?;
204222
filter_symbols(&merged_path, &filter_path)?;
205223

206-
let obj = File::open(&merged_path)?;
207-
let header = Header::new(
208-
merged_path
209-
.file_name()
210-
.unwrap()
211-
.to_string_lossy()
212-
.as_bytes()
213-
.to_vec(),
214-
obj.metadata()?.len(),
215-
);
216-
output.append(&header, &obj)?;
224+
output.append_obj(merged_path)?;
225+
output.close()?;
217226

218227
Ok(())
219228
}

0 commit comments

Comments
 (0)