@@ -36,6 +36,17 @@ pub struct Archive<'a> {
36
36
maybe_ar_prog : Option < String >
37
37
}
38
38
39
+ /// Helper for adding many files to an archive with a single invocation of
40
+ /// `ar`.
41
+ #[ must_use = "must call build() to finish building the archive" ]
42
+ pub struct ArchiveBuilder < ' a > {
43
+ archive : Archive < ' a > ,
44
+ work_dir : TempDir ,
45
+ /// Filename of each member that should be added to the archive.
46
+ members : Vec < Path > ,
47
+ should_update_symbols : bool ,
48
+ }
49
+
39
50
fn run_ar ( handler : & ErrorHandler , maybe_ar_prog : & Option < String > ,
40
51
args : & str , cwd : Option < & Path > ,
41
52
paths : & [ & Path ] ) -> ProcessOutput {
@@ -85,10 +96,8 @@ fn run_ar(handler: &ErrorHandler, maybe_ar_prog: &Option<String>,
85
96
}
86
97
87
98
impl < ' a > Archive < ' a > {
88
- /// Initializes a new static archive with the given object file
89
- pub fn create < ' b > ( config : ArchiveConfig < ' a > , initial_object : & ' b Path ) -> Archive < ' a > {
99
+ fn new ( config : ArchiveConfig < ' a > ) -> Archive < ' a > {
90
100
let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
91
- run_ar ( handler, & maybe_ar_prog, "crus" , None , [ & dst, initial_object] ) ;
92
101
Archive {
93
102
handler : handler,
94
103
dst : dst,
@@ -100,17 +109,47 @@ impl<'a> Archive<'a> {
100
109
101
110
/// Opens an existing static archive
102
111
pub fn open ( config : ArchiveConfig < ' a > ) -> Archive < ' a > {
103
- let ArchiveConfig { handler, dst, lib_search_paths, os, maybe_ar_prog } = config;
104
- assert ! ( dst. exists( ) ) ;
105
- Archive {
106
- handler : handler,
107
- dst : dst,
108
- lib_search_paths : lib_search_paths,
109
- os : os,
110
- maybe_ar_prog : maybe_ar_prog
112
+ let archive = Archive :: new ( config) ;
113
+ assert ! ( archive. dst. exists( ) ) ;
114
+ archive
115
+ }
116
+
117
+ /// Removes a file from this archive
118
+ pub fn remove_file ( & mut self , file : & str ) {
119
+ run_ar ( self . handler , & self . maybe_ar_prog , "d" , None , [ & self . dst , & Path :: new ( file) ] ) ;
120
+ }
121
+
122
+ /// Lists all files in an archive
123
+ pub fn files ( & self ) -> Vec < String > {
124
+ let output = run_ar ( self . handler , & self . maybe_ar_prog , "t" , None , [ & self . dst ] ) ;
125
+ let output = str:: from_utf8 ( output. output . as_slice ( ) ) . unwrap ( ) ;
126
+ // use lines_any because windows delimits output with `\r\n` instead of
127
+ // just `\n`
128
+ output. lines_any ( ) . map ( |s| s. to_string ( ) ) . collect ( )
129
+ }
130
+
131
+ /// Creates an `ArchiveBuilder` for adding files to this archive.
132
+ pub fn extend ( self ) -> ArchiveBuilder < ' a > {
133
+ ArchiveBuilder :: new ( self )
134
+ }
135
+ }
136
+
137
+ impl < ' a > ArchiveBuilder < ' a > {
138
+ fn new ( archive : Archive < ' a > ) -> ArchiveBuilder < ' a > {
139
+ ArchiveBuilder {
140
+ archive : archive,
141
+ work_dir : TempDir :: new ( "rsar" ) . unwrap ( ) ,
142
+ members : vec ! [ ] ,
143
+ should_update_symbols : false ,
111
144
}
112
145
}
113
146
147
+ /// Create a new static archive, ready for adding files.
148
+ pub fn create ( config : ArchiveConfig < ' a > ) -> ArchiveBuilder < ' a > {
149
+ let archive = Archive :: new ( config) ;
150
+ ArchiveBuilder :: new ( archive)
151
+ }
152
+
114
153
/// Adds all of the contents of a native library to this archive. This will
115
154
/// search in the relevant locations for a library named `name`.
116
155
pub fn add_native_library ( & mut self , name : & str ) -> io:: IoResult < ( ) > {
@@ -135,48 +174,96 @@ impl<'a> Archive<'a> {
135
174
}
136
175
137
176
/// Adds an arbitrary file to this archive
138
- pub fn add_file ( & mut self , file : & Path , has_symbols : bool ) {
139
- let cmd = if has_symbols { "r" } else { "rS" } ;
140
- run_ar ( self . handler , & self . maybe_ar_prog , cmd, None , [ & self . dst , file] ) ;
141
- }
142
-
143
- /// Removes a file from this archive
144
- pub fn remove_file ( & mut self , file : & str ) {
145
- run_ar ( self . handler , & self . maybe_ar_prog , "d" , None , [ & self . dst , & Path :: new ( file) ] ) ;
177
+ pub fn add_file ( & mut self , file : & Path ) -> io:: IoResult < ( ) > {
178
+ let filename = Path :: new ( file. filename ( ) . unwrap ( ) ) ;
179
+ let new_file = self . work_dir . path ( ) . join ( & filename) ;
180
+ try!( fs:: copy ( file, & new_file) ) ;
181
+ self . members . push ( filename) ;
182
+ Ok ( ( ) )
146
183
}
147
184
148
- /// Updates all symbols in the archive (runs 'ar s' over it)
185
+ /// Indicate that the next call to `build` should updates all symbols in
186
+ /// the archive (run 'ar s' over it).
149
187
pub fn update_symbols ( & mut self ) {
150
- run_ar ( self . handler , & self . maybe_ar_prog , "s" , None , [ & self . dst ] ) ;
188
+ self . should_update_symbols = true ;
151
189
}
152
190
153
- /// Lists all files in an archive
154
- pub fn files ( & self ) -> Vec < String > {
155
- let output = run_ar ( self . handler , & self . maybe_ar_prog , "t" , None , [ & self . dst ] ) ;
156
- let output = str:: from_utf8 ( output. output . as_slice ( ) ) . unwrap ( ) ;
157
- // use lines_any because windows delimits output with `\r\n` instead of
158
- // just `\n`
159
- output. lines_any ( ) . map ( |s| s. to_string ( ) ) . collect ( )
191
+ /// Combine the provided files, rlibs, and native libraries into a single
192
+ /// `Archive`.
193
+ pub fn build ( self ) -> Archive < ' a > {
194
+ // Get an absolute path to the destination, so `ar` will work even
195
+ // though we run it from `self.work_dir`.
196
+ let abs_dst = os:: getcwd ( ) . join ( & self . archive . dst ) ;
197
+ assert ! ( !abs_dst. is_relative( ) ) ;
198
+ let mut args = vec ! [ & abs_dst] ;
199
+ let mut total_len = abs_dst. as_vec ( ) . len ( ) ;
200
+
201
+ if self . members . is_empty ( ) {
202
+ // OSX `ar` does not allow using `r` with no members, but it does
203
+ // allow running `ar s file.a` to update symbols only.
204
+ if self . should_update_symbols {
205
+ run_ar ( self . archive . handler , & self . archive . maybe_ar_prog ,
206
+ "s" , Some ( self . work_dir . path ( ) ) , args. as_slice ( ) ) ;
207
+ }
208
+ return self . archive ;
209
+ }
210
+
211
+ // Don't allow the total size of `args` to grow beyond 32,000 bytes.
212
+ // Windows will raise an error if the argument string is longer than
213
+ // 32,768, and we leave a bit of extra space for the program name.
214
+ static ARG_LENGTH_LIMIT : uint = 32000 ;
215
+
216
+ for member_name in self . members . iter ( ) {
217
+ let len = member_name. as_vec ( ) . len ( ) ;
218
+
219
+ // `len + 1` to account for the space that's inserted before each
220
+ // argument. (Windows passes command-line arguments as a single
221
+ // string, not an array of strings.)
222
+ if total_len + len + 1 > ARG_LENGTH_LIMIT {
223
+ // Add the archive members seen so far, without updating the
224
+ // symbol table (`S`).
225
+ run_ar ( self . archive . handler , & self . archive . maybe_ar_prog ,
226
+ "cruS" , Some ( self . work_dir . path ( ) ) , args. as_slice ( ) ) ;
227
+
228
+ args. clear ( ) ;
229
+ args. push ( & abs_dst) ;
230
+ total_len = abs_dst. as_vec ( ) . len ( ) ;
231
+ }
232
+
233
+ args. push ( member_name) ;
234
+ total_len += len + 1 ;
235
+ }
236
+
237
+ // Add the remaining archive members, and update the symbol table if
238
+ // necessary.
239
+ let flags = if self . should_update_symbols { "crus" } else { "cruS" } ;
240
+ run_ar ( self . archive . handler , & self . archive . maybe_ar_prog ,
241
+ flags, Some ( self . work_dir . path ( ) ) , args. as_slice ( ) ) ;
242
+
243
+ self . archive
160
244
}
161
245
162
246
fn add_archive ( & mut self , archive : & Path , name : & str ,
163
247
skip : & [ & str ] ) -> io:: IoResult < ( ) > {
164
248
let loc = TempDir :: new ( "rsar" ) . unwrap ( ) ;
165
249
166
- // First, extract the contents of the archive to a temporary directory
250
+ // First, extract the contents of the archive to a temporary directory.
251
+ // We don't unpack directly into `self.work_dir` due to the possibility
252
+ // of filename collisions.
167
253
let archive = os:: make_absolute ( archive) ;
168
- run_ar ( self . handler , & self . maybe_ar_prog , "x" , Some ( loc. path ( ) ) , [ & archive] ) ;
254
+ run_ar ( self . archive . handler , & self . archive . maybe_ar_prog ,
255
+ "x" , Some ( loc. path ( ) ) , [ & archive] ) ;
169
256
170
257
// Next, we must rename all of the inputs to "guaranteed unique names".
171
- // The reason for this is that archives are keyed off the name of the
172
- // files, so if two files have the same name they will override one
173
- // another in the archive (bad).
258
+ // We move each file into `self.work_dir` under its new unique name.
259
+ // The reason for this renaming is that archives are keyed off the name
260
+ // of the files, so if two files have the same name they will override
261
+ // one another in the archive (bad).
174
262
//
175
263
// We skip any files explicitly desired for skipping, and we also skip
176
264
// all SYMDEF files as these are just magical placeholders which get
177
265
// re-created when we make a new archive anyway.
178
266
let files = try!( fs:: readdir ( loc. path ( ) ) ) ;
179
- let mut inputs = Vec :: new ( ) ;
180
267
for file in files. iter ( ) {
181
268
let filename = file. filename_str ( ) . unwrap ( ) ;
182
269
if skip. iter ( ) . any ( |s| * s == filename) { continue }
@@ -192,29 +279,23 @@ impl<'a> Archive<'a> {
192
279
} else {
193
280
filename
194
281
} ;
195
- let new_filename = file . with_filename ( filename) ;
282
+ let new_filename = self . work_dir . path ( ) . join ( filename. as_slice ( ) ) ;
196
283
try!( fs:: rename ( file, & new_filename) ) ;
197
- inputs . push ( new_filename ) ;
284
+ self . members . push ( Path :: new ( filename ) ) ;
198
285
}
199
- if inputs. len ( ) == 0 { return Ok ( ( ) ) }
200
-
201
- // Finally, add all the renamed files to this archive
202
- let mut args = vec ! ( & self . dst) ;
203
- args. extend ( inputs. iter ( ) ) ;
204
- run_ar ( self . handler , & self . maybe_ar_prog , "r" , None , args. as_slice ( ) ) ;
205
286
Ok ( ( ) )
206
287
}
207
288
208
289
fn find_library ( & self , name : & str ) -> Path {
209
- let ( osprefix, osext) = match self . os {
290
+ let ( osprefix, osext) = match self . archive . os {
210
291
abi:: OsWin32 => ( "" , "lib" ) , _ => ( "lib" , "a" ) ,
211
292
} ;
212
293
// On Windows, static libraries sometimes show up as libfoo.a and other
213
294
// times show up as foo.lib
214
295
let oslibname = format ! ( "{}{}.{}" , osprefix, name, osext) ;
215
296
let unixlibname = format ! ( "lib{}.a" , name) ;
216
297
217
- for path in self . lib_search_paths . iter ( ) {
298
+ for path in self . archive . lib_search_paths . iter ( ) {
218
299
debug ! ( "looking for {} inside {}" , name, path. display( ) ) ;
219
300
let test = path. join ( oslibname. as_slice ( ) ) ;
220
301
if test. exists ( ) { return test }
@@ -223,9 +304,9 @@ impl<'a> Archive<'a> {
223
304
if test. exists ( ) { return test }
224
305
}
225
306
}
226
- self . handler . fatal ( format ! ( "could not find native static library `{}`, \
227
- perhaps an -L flag is missing?",
228
- name) . as_slice ( ) ) ;
307
+ self . archive . handler . fatal ( format ! ( "could not find native static library `{}`, \
308
+ perhaps an -L flag is missing?",
309
+ name) . as_slice ( ) ) ;
229
310
}
230
311
}
231
312
0 commit comments