@@ -32,6 +32,12 @@ struct Transaction {
32
32
bins : Vec < PathBuf > ,
33
33
}
34
34
35
+ impl Transaction {
36
+ fn success ( mut self ) {
37
+ self . bins . clear ( ) ;
38
+ }
39
+ }
40
+
35
41
impl Drop for Transaction {
36
42
fn drop ( & mut self ) {
37
43
for bin in self . bins . iter ( ) {
@@ -44,7 +50,8 @@ pub fn install(root: Option<&str>,
44
50
krate : Option < & str > ,
45
51
source_id : & SourceId ,
46
52
vers : Option < & str > ,
47
- opts : & ops:: CompileOptions ) -> CargoResult < ( ) > {
53
+ opts : & ops:: CompileOptions ,
54
+ force : bool ) -> CargoResult < ( ) > {
48
55
let config = opts. config ;
49
56
let root = try!( resolve_root ( root, config) ) ;
50
57
let ( pkg, source) = if source_id. is_git ( ) {
@@ -77,7 +84,7 @@ pub fn install(root: Option<&str>,
77
84
let metadata = try!( metadata ( config, & root) ) ;
78
85
let list = try!( read_crate_list ( metadata. file ( ) ) ) ;
79
86
let dst = metadata. parent ( ) . join ( "bin" ) ;
80
- try!( check_overwrites ( & dst, & pkg, & opts. filter , & list) ) ;
87
+ try!( check_overwrites ( & dst, & pkg, & opts. filter , & list, force ) ) ;
81
88
}
82
89
83
90
let mut td_opt = None ;
@@ -102,40 +109,116 @@ pub fn install(root: Option<&str>,
102
109
human ( format ! ( "failed to compile `{}`, intermediate artifacts can be \
103
110
found at `{}`", pkg, target_dir. display( ) ) )
104
111
} ) ) ;
112
+ let binaries: Vec < ( & str , & Path ) > = try!( compile. binaries . iter ( ) . map ( |bin| {
113
+ let name = bin. file_name ( ) . unwrap ( ) ;
114
+ if let Some ( s) = name. to_str ( ) {
115
+ Ok ( ( s, bin. as_ref ( ) ) )
116
+ } else {
117
+ bail ! ( "Binary `{:?}` name can't be serialized into string" , name)
118
+ }
119
+ } ) . collect :: < CargoResult < _ > > ( ) ) ;
105
120
106
121
let metadata = try!( metadata ( config, & root) ) ;
107
122
let mut list = try!( read_crate_list ( metadata. file ( ) ) ) ;
108
123
let dst = metadata. parent ( ) . join ( "bin" ) ;
109
- try!( check_overwrites ( & dst, & pkg, & opts. filter , & list) ) ;
124
+ let duplicates = try!( check_overwrites ( & dst, & pkg, & opts. filter , & list, force ) ) ;
110
125
111
- let mut t = Transaction { bins : Vec :: new ( ) } ;
112
126
try!( fs:: create_dir_all ( & dst) ) ;
113
- for bin in compile. binaries . iter ( ) {
114
- let dst = dst. join ( bin. file_name ( ) . unwrap ( ) ) ;
127
+
128
+ // Copy all binaries to a temporary directory under `dst` first, catching
129
+ // some failure modes (e.g. out of space) before touching the existing
130
+ // binaries. This directory will get cleaned up via RAII.
131
+ let staging_dir = try!( TempDir :: new_in ( & dst, "cargo-install" ) ) ;
132
+ for & ( bin, src) in binaries. iter ( ) {
133
+ let dst = staging_dir. path ( ) . join ( bin) ;
134
+ try!( fs:: copy ( src, & dst) . chain_error ( || {
135
+ human ( format ! ( "failed to copy `{}` to `{}`" , src. display( ) ,
136
+ dst. display( ) ) )
137
+ } ) ) ;
138
+ }
139
+
140
+ let ( to_replace, to_install) : ( Vec < & str > , Vec < & str > ) =
141
+ binaries. iter ( ) . map ( |& ( bin, _) | bin)
142
+ . partition ( |& bin| duplicates. contains_key ( bin) ) ;
143
+
144
+ let mut installed = Transaction { bins : Vec :: new ( ) } ;
145
+
146
+ // Move the temporary copies into `dst` starting with new binaries.
147
+ for bin in to_install. iter ( ) {
148
+ let src = staging_dir. path ( ) . join ( bin) ;
149
+ let dst = dst. join ( bin) ;
115
150
try!( config. shell ( ) . status ( "Installing" , dst. display ( ) ) ) ;
116
- try!( fs:: copy ( & bin , & dst) . chain_error ( || {
117
- human ( format ! ( "failed to copy `{}` to `{}`" , bin . display( ) ,
151
+ try!( fs:: rename ( & src , & dst) . chain_error ( || {
152
+ human ( format ! ( "failed to move `{}` to `{}`" , src . display( ) ,
118
153
dst. display( ) ) )
119
154
} ) ) ;
120
- t . bins . push ( dst) ;
155
+ installed . bins . push ( dst) ;
121
156
}
122
157
158
+ // Repeat for binaries which replace existing ones but don't pop the error
159
+ // up until after updating metadata.
160
+ let mut replaced_names = Vec :: new ( ) ;
161
+ let result = {
162
+ let mut try_install = || -> CargoResult < ( ) > {
163
+ for & bin in to_replace. iter ( ) {
164
+ let src = staging_dir. path ( ) . join ( bin) ;
165
+ let dst = dst. join ( bin) ;
166
+ try!( config. shell ( ) . status ( "Replacing" , dst. display ( ) ) ) ;
167
+ try!( fs:: rename ( & src, & dst) . chain_error ( || {
168
+ human ( format ! ( "failed to move `{}` to `{}`" , src. display( ) ,
169
+ dst. display( ) ) )
170
+ } ) ) ;
171
+ replaced_names. push ( bin) ;
172
+ }
173
+ Ok ( ( ) )
174
+ } ;
175
+ try_install ( )
176
+ } ;
177
+
178
+ // Update records of replaced binaries.
179
+ for & bin in replaced_names. iter ( ) {
180
+ if let Some ( & Some ( ref p) ) = duplicates. get ( bin) {
181
+ if let Some ( set) = list. v1 . get_mut ( p) {
182
+ set. remove ( bin) ;
183
+ }
184
+ }
185
+ list. v1 . entry ( pkg. package_id ( ) . clone ( ) )
186
+ . or_insert_with ( || BTreeSet :: new ( ) )
187
+ . insert ( bin. to_string ( ) ) ;
188
+ }
189
+
190
+ // Remove empty metadata lines.
191
+ let pkgs = list. v1 . iter ( )
192
+ . filter_map ( |( p, set) | if set. is_empty ( ) { Some ( p. clone ( ) ) } else { None } )
193
+ . collect :: < Vec < _ > > ( ) ;
194
+ for p in pkgs. iter ( ) {
195
+ list. v1 . remove ( p) ;
196
+ }
197
+
198
+ // If installation was successful record newly installed binaries.
199
+ if result. is_ok ( ) {
200
+ list. v1 . entry ( pkg. package_id ( ) . clone ( ) )
201
+ . or_insert_with ( || BTreeSet :: new ( ) )
202
+ . extend ( to_install. iter ( ) . map ( |s| s. to_string ( ) ) ) ;
203
+ }
204
+
205
+ let write_result = write_crate_list ( metadata. file ( ) , list) ;
206
+ match write_result {
207
+ // Replacement error (if any) isn't actually caused by write error
208
+ // but this seems to be the only way to show both.
209
+ Err ( err) => try!( result. chain_error ( || err) ) ,
210
+ Ok ( _) => try!( result) ,
211
+ }
212
+
213
+ // Reaching here means all actions have succeeded. Clean up.
214
+ installed. success ( ) ;
123
215
if !source_id. is_path ( ) {
124
216
// Don't bother grabbing a lock as we're going to blow it all away
125
217
// anyway.
126
218
let target_dir = target_dir. into_path_unlocked ( ) ;
127
219
try!( fs:: remove_dir_all ( & target_dir) ) ;
128
220
}
129
221
130
- list. v1 . entry ( pkg. package_id ( ) . clone ( ) ) . or_insert_with ( || {
131
- BTreeSet :: new ( )
132
- } ) . extend ( t. bins . iter ( ) . map ( |t| {
133
- t. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . into_owned ( )
134
- } ) ) ;
135
- try!( write_crate_list ( metadata. file ( ) , list) ) ;
136
-
137
- t. bins . truncate ( 0 ) ;
138
-
139
222
// Print a warning that if this directory isn't in PATH that they won't be
140
223
// able to run these commands.
141
224
let path = env:: var_os ( "PATH" ) . unwrap_or ( OsString :: new ( ) ) ;
@@ -225,38 +308,61 @@ fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
225
308
fn check_overwrites ( dst : & Path ,
226
309
pkg : & Package ,
227
310
filter : & ops:: CompileFilter ,
228
- prev : & CrateListingV1 ) -> CargoResult < ( ) > {
311
+ prev : & CrateListingV1 ,
312
+ force : bool ) -> CargoResult < BTreeMap < String , Option < PackageId > > > {
313
+ if let CompileFilter :: Everything = * filter {
314
+ // If explicit --bin or --example flags were passed then those'll
315
+ // get checked during cargo_compile, we only care about the "build
316
+ // everything" case here
317
+ if pkg. targets ( ) . iter ( ) . filter ( |t| t. is_bin ( ) ) . next ( ) . is_none ( ) {
318
+ bail ! ( "specified package has no binaries" )
319
+ }
320
+ }
321
+ let duplicates = find_duplicates ( dst, pkg, filter, prev) ;
322
+ if force || duplicates. is_empty ( ) {
323
+ return Ok ( duplicates)
324
+ }
325
+ // Format the error message.
326
+ let mut msg = String :: new ( ) ;
327
+ for ( ref bin, p) in duplicates. iter ( ) {
328
+ msg. push_str ( & format ! ( "binary `{}` already exists in destination" , bin) ) ;
329
+ if let Some ( p) = p. as_ref ( ) {
330
+ msg. push_str ( & format ! ( " as part of `{}`\n " , p) ) ;
331
+ } else {
332
+ msg. push_str ( "\n " ) ;
333
+ }
334
+ }
335
+ msg. push_str ( "Add --force to overwrite" ) ;
336
+ Err ( human ( msg) )
337
+ }
338
+
339
+ fn find_duplicates ( dst : & Path ,
340
+ pkg : & Package ,
341
+ filter : & ops:: CompileFilter ,
342
+ prev : & CrateListingV1 ) -> BTreeMap < String , Option < PackageId > > {
229
343
let check = |name| {
230
344
let name = format ! ( "{}{}" , name, env:: consts:: EXE_SUFFIX ) ;
231
345
if fs:: metadata ( dst. join ( & name) ) . is_err ( ) {
232
- return Ok ( ( ) )
233
- }
234
- let mut msg = format ! ( "binary `{}` already exists in destination" , name ) ;
235
- if let Some ( ( p , _ ) ) = prev . v1 . iter ( ) . find ( | & ( _ , v ) | v . contains ( & name ) ) {
236
- msg . push_str ( & format ! ( " as part of `{}`" , p ) ) ;
346
+ None
347
+ } else if let Some ( ( p , _ ) ) = prev . v1 . iter ( ) . find ( | & ( _ , v ) | v . contains ( & name ) ) {
348
+ Some ( ( name , Some ( p . clone ( ) ) ) )
349
+ } else {
350
+ Some ( ( name , None ) )
237
351
}
238
- Err ( human ( msg) )
239
352
} ;
240
353
match * filter {
241
354
CompileFilter :: Everything => {
242
- // If explicit --bin or --example flags were passed then those'll
243
- // get checked during cargo_compile, we only care about the "build
244
- // everything" case here
245
- if pkg. targets ( ) . iter ( ) . filter ( |t| t. is_bin ( ) ) . next ( ) . is_none ( ) {
246
- bail ! ( "specified package has no binaries" )
247
- }
248
-
249
- for target in pkg. targets ( ) . iter ( ) . filter ( |t| t. is_bin ( ) ) {
250
- try!( check ( target. name ( ) ) ) ;
251
- }
355
+ pkg. targets ( ) . iter ( )
356
+ . filter ( |t| t. is_bin ( ) )
357
+ . filter_map ( |t| check ( t. name ( ) ) )
358
+ . collect ( )
252
359
}
253
360
CompileFilter :: Only { bins, examples, .. } => {
254
- for bin in bins. iter ( ) . chain ( examples) {
255
- try! ( check ( bin ) ) ;
256
- }
361
+ bins. iter ( ) . chain ( examples)
362
+ . filter_map ( |t| check ( t ) )
363
+ . collect ( )
257
364
}
258
365
}
259
- Ok ( ( ) )
260
366
}
261
367
262
368
fn read_crate_list ( mut file : & File ) -> CargoResult < CrateListingV1 > {
0 commit comments