]> git.lizzy.rs Git - rust.git/blob - src/cargo/cargo.rs
Merge remote-tracking branch 'original/incoming' into incoming
[rust.git] / src / cargo / cargo.rs
1 // cargo.rs - Rust package manager
2
3 #[legacy_exports];
4
5 use syntax::{ast, codemap, parse, visit, attr};
6 use syntax::diagnostic::span_handler;
7 use codemap::span;
8 use rustc::metadata::filesearch::{get_cargo_root, get_cargo_root_nearest,
9                                      get_cargo_sysroot, libdir};
10 use syntax::diagnostic;
11
12 use result::{Ok, Err};
13 use io::WriterUtil;
14 use send_map::linear::LinearMap;
15 use std::{map, json, tempfile, term, sort, getopts};
16 use map::HashMap;
17 use to_str::to_str;
18 use getopts::{optflag, optopt, opt_present};
19 use dvec::DVec;
20
21 struct Package {
22     name: ~str,
23     uuid: ~str,
24     url: ~str,
25     method: ~str,
26     description: ~str,
27     reference: Option<~str>,
28     tags: ~[~str],
29     versions: ~[(~str, ~str)]
30 }
31
32 impl Package : cmp::Ord {
33     pure fn lt(other: &Package) -> bool {
34         if self.name.lt(&(*other).name) { return true; }
35         if (*other).name.lt(&self.name) { return false; }
36         if self.uuid.lt(&(*other).uuid) { return true; }
37         if (*other).uuid.lt(&self.uuid) { return false; }
38         if self.url.lt(&(*other).url) { return true; }
39         if (*other).url.lt(&self.url) { return false; }
40         if self.method.lt(&(*other).method) { return true; }
41         if (*other).method.lt(&self.method) { return false; }
42         if self.description.lt(&(*other).description) { return true; }
43         if (*other).description.lt(&self.description) { return false; }
44         if self.tags.lt(&(*other).tags) { return true; }
45         if (*other).tags.lt(&self.tags) { return false; }
46         if self.versions.lt(&(*other).versions) { return true; }
47         return false;
48     }
49     pure fn le(other: &Package) -> bool { !(*other).lt(&self) }
50     pure fn ge(other: &Package) -> bool { !self.lt(other)     }
51     pure fn gt(other: &Package) -> bool { (*other).lt(&self)  }
52 }
53
54 struct Source {
55     name: ~str,
56     mut url: ~str,
57     mut method: ~str,
58     mut key: Option<~str>,
59     mut keyfp: Option<~str>,
60     packages: DVec<Package>
61 }
62
63 struct Cargo {
64     pgp: bool,
65     root: Path,
66     installdir: Path,
67     bindir: Path,
68     libdir: Path,
69     workdir: Path,
70     sourcedir: Path,
71     sources: map::HashMap<~str, @Source>,
72     mut current_install: ~str,
73     dep_cache: map::HashMap<~str, bool>,
74     opts: Options
75 }
76
77 struct Crate {
78     name: ~str,
79     vers: ~str,
80     uuid: ~str,
81     desc: Option<~str>,
82     sigs: Option<~str>,
83     crate_type: Option<~str>,
84     deps: ~[~str]
85 }
86
87 struct Options {
88     test: bool,
89     mode: Mode,
90     free: ~[~str],
91     help: bool,
92 }
93
94 enum Mode { SystemMode, UserMode, LocalMode }
95
96 impl Mode : cmp::Eq {
97     pure fn eq(other: &Mode) -> bool {
98         (self as uint) == ((*other) as uint)
99     }
100     pure fn ne(other: &Mode) -> bool { !self.eq(other) }
101 }
102
103 fn opts() -> ~[getopts::Opt] {
104     ~[optflag(~"g"), optflag(~"G"), optflag(~"test"),
105      optflag(~"h"), optflag(~"help")]
106 }
107
108 fn info(msg: ~str) {
109     let out = io::stdout();
110
111     if term::color_supported() {
112         term::fg(out, term::color_green);
113         out.write_str(~"info: ");
114         term::reset(out);
115         out.write_line(msg);
116     } else { out.write_line(~"info: " + msg); }
117 }
118
119 fn warn(msg: ~str) {
120     let out = io::stdout();
121
122     if term::color_supported() {
123         term::fg(out, term::color_yellow);
124         out.write_str(~"warning: ");
125         term::reset(out);
126         out.write_line(msg);
127     }else { out.write_line(~"warning: " + msg); }
128 }
129
130 fn error(msg: ~str) {
131     let out = io::stdout();
132
133     if term::color_supported() {
134         term::fg(out, term::color_red);
135         out.write_str(~"error: ");
136         term::reset(out);
137         out.write_line(msg);
138     }
139     else { out.write_line(~"error: " + msg); }
140 }
141
142 fn is_uuid(id: ~str) -> bool {
143     let parts = str::split_str(id, ~"-");
144     if vec::len(parts) == 5u {
145         let mut correct = 0u;
146         for vec::eachi(parts) |i, part| {
147             fn is_hex_digit(+ch: char) -> bool {
148                 ('0' <= ch && ch <= '9') ||
149                 ('a' <= ch && ch <= 'f') ||
150                 ('A' <= ch && ch <= 'F')
151             }
152
153             if !part.all(is_hex_digit) {
154                 return false;
155             }
156
157             match i {
158                 0u => {
159                     if part.len() == 8u {
160                         correct += 1u;
161                     }
162                 }
163                 1u | 2u | 3u => {
164                     if part.len() == 4u {
165                         correct += 1u;
166                     }
167                 }
168                 4u => {
169                     if part.len() == 12u {
170                         correct += 1u;
171                     }
172                 }
173                 _ => { }
174             }
175         }
176         if correct >= 5u {
177             return true;
178         }
179     }
180     return false;
181 }
182
183 #[test]
184 fn test_is_uuid() {
185     assert is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaafAF09");
186     assert !is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaaa-aaaaaaaaaaaa");
187     assert !is_uuid(~"");
188     assert !is_uuid(~"aaaaaaaa-aaa -aaaa-aaaa-aaaaaaaaaaaa");
189     assert !is_uuid(~"aaaaaaaa-aaa!-aaaa-aaaa-aaaaaaaaaaaa");
190     assert !is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-a");
191     assert !is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaป");
192 }
193
194 // FIXME (#2661): implement url/URL parsing so we don't have to resort
195 // to weak checks
196
197 fn has_archive_extension(p: ~str) -> bool {
198     str::ends_with(p, ~".tar") ||
199     str::ends_with(p, ~".tar.gz") ||
200     str::ends_with(p, ~".tar.bz2") ||
201     str::ends_with(p, ~".tar.Z") ||
202     str::ends_with(p, ~".tar.lz") ||
203     str::ends_with(p, ~".tar.xz") ||
204     str::ends_with(p, ~".tgz") ||
205     str::ends_with(p, ~".tbz") ||
206     str::ends_with(p, ~".tbz2") ||
207     str::ends_with(p, ~".tb2") ||
208     str::ends_with(p, ~".taz") ||
209     str::ends_with(p, ~".tlz") ||
210     str::ends_with(p, ~".txz")
211 }
212
213 fn is_archive_path(u: ~str) -> bool {
214     has_archive_extension(u) && os::path_exists(&Path(u))
215 }
216
217 fn is_archive_url(u: ~str) -> bool {
218     // FIXME (#2661): this requires the protocol bit - if we had proper
219     // url parsing, we wouldn't need it
220
221     match str::find_str(u, ~"://") {
222         option::Some(_) => has_archive_extension(u),
223         _ => false
224     }
225 }
226
227 fn is_git_url(url: ~str) -> bool {
228     if str::ends_with(url, ~"/") { str::ends_with(url, ~".git/") }
229     else {
230         str::starts_with(url, ~"git://") || str::ends_with(url, ~".git")
231     }
232 }
233
234 fn assume_source_method(url: ~str) -> ~str {
235     if is_git_url(url) {
236         return ~"git";
237     }
238     if str::starts_with(url, ~"file://") || os::path_exists(&Path(url)) {
239         return ~"file";
240     }
241
242     ~"curl"
243 }
244
245 fn load_link(mis: ~[@ast::meta_item]) -> (Option<~str>,
246                                          Option<~str>,
247                                          Option<~str>) {
248     let mut name = None;
249     let mut vers = None;
250     let mut uuid = None;
251     for mis.each |a| {
252         match a.node {
253             ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) => {
254                 match v {
255                     ~"name" => name = Some(*s),
256                     ~"vers" => vers = Some(*s),
257                     ~"uuid" => uuid = Some(*s),
258                     _ => { }
259                 }
260             }
261             _ => fail ~"load_link: meta items must be name-values"
262         }
263     }
264     (name, vers, uuid)
265 }
266
267 fn load_crate(filename: &Path) -> Option<Crate> {
268     let sess = parse::new_parse_sess(None);
269     let c = parse::parse_crate_from_crate_file(filename, ~[], sess);
270
271     let mut name = None;
272     let mut vers = None;
273     let mut uuid = None;
274     let mut desc = None;
275     let mut sigs = None;
276     let mut crate_type = None;
277
278     for c.node.attrs.each |a| {
279         match a.node.value.node {
280             ast::meta_name_value(v, {node: ast::lit_str(_), span: _}) => {
281                 match v {
282                     ~"desc" => desc = Some(v),
283                     ~"sigs" => sigs = Some(v),
284                     ~"crate_type" => crate_type = Some(v),
285                     _ => { }
286                 }
287             }
288             ast::meta_list(v, mis) => {
289                 if v == ~"link" {
290                     let (n, v, u) = load_link(mis);
291                     name = n;
292                     vers = v;
293                     uuid = u;
294                 }
295             }
296             _ => {
297                 fail ~"crate attributes may not contain " +
298                      ~"meta_words";
299             }
300         }
301     }
302
303     type env = @{
304         mut deps: ~[~str]
305     };
306
307     fn goto_view_item(ps: syntax::parse::parse_sess, e: env,
308                       i: @ast::view_item) {
309         match i.node {
310             ast::view_item_use(ident, metas, _) => {
311                 let name_items =
312                     attr::find_meta_items_by_name(metas, ~"name");
313                 let m = if name_items.is_empty() {
314                     metas + ~[attr::mk_name_value_item_str(
315                         ~"name", *ps.interner.get(ident))]
316                 } else {
317                     metas
318                 };
319                 let mut attr_name = ident;
320                 let mut attr_vers = ~"";
321                 let mut attr_from = ~"";
322
323               for m.each |item| {
324                     match attr::get_meta_item_value_str(*item) {
325                         Some(value) => {
326                             let name = attr::get_meta_item_name(*item);
327
328                             match name {
329                                 ~"vers" => attr_vers = value,
330                                 ~"from" => attr_from = value,
331                                 _ => ()
332                             }
333                         }
334                         None => ()
335                     }
336                 }
337
338                 let query = if !str::is_empty(attr_from) {
339                     attr_from
340                 } else {
341                     if !str::is_empty(attr_vers) {
342                         ps.interner.get(attr_name) + ~"@" + attr_vers
343                     } else { *ps.interner.get(attr_name) }
344                 };
345
346                 match *ps.interner.get(attr_name) {
347                     ~"std" | ~"core" => (),
348                     _ => e.deps.push(query)
349                 }
350             }
351             _ => ()
352         }
353     }
354     fn goto_item(_e: env, _i: @ast::item) {
355     }
356
357     let e = @{
358         mut deps: ~[]
359     };
360     let v = visit::mk_simple_visitor(@{
361         visit_view_item: |a| goto_view_item(sess, e, a),
362         visit_item: |a| goto_item(e, a),
363         .. *visit::default_simple_visitor()
364     });
365
366     visit::visit_crate(*c, (), v);
367
368     let deps = copy e.deps;
369
370     match (name, vers, uuid) {
371         (Some(name0), Some(vers0), Some(uuid0)) => {
372             Some(Crate {
373                 name: name0,
374                 vers: vers0,
375                 uuid: uuid0,
376                 desc: desc,
377                 sigs: sigs,
378                 crate_type: crate_type,
379                 deps: deps })
380         }
381         _ => return None
382     }
383 }
384
385 fn print(s: ~str) {
386     io::stdout().write_line(s);
387 }
388
389 fn rest(s: ~str, start: uint) -> ~str {
390     if (start >= str::len(s)) {
391         ~""
392     } else {
393         str::slice(s, start, str::len(s))
394     }
395 }
396
397 fn need_dir(s: &Path) {
398     if os::path_is_dir(s) { return; }
399     if !os::make_dir(s, 493_i32 /* oct: 755 */) {
400         fail fmt!("can't make_dir %s", s.to_str());
401     }
402 }
403
404 fn valid_pkg_name(s: &str) -> bool {
405     fn is_valid_digit(+c: char) -> bool {
406         ('0' <= c && c <= '9') ||
407         ('a' <= c && c <= 'z') ||
408         ('A' <= c && c <= 'Z') ||
409         c == '-' ||
410         c == '_'
411     }
412
413     s.all(is_valid_digit)
414 }
415
416 fn parse_source(name: ~str, j: &json::Json) -> @Source {
417     if !valid_pkg_name(name) {
418         fail fmt!("'%s' is an invalid source name", name);
419     }
420
421     match *j {
422         json::Object(j) => {
423             let mut url = match j.find(&~"url") {
424                 Some(json::String(u)) => u,
425                 _ => fail ~"needed 'url' field in source"
426             };
427             let method = match j.find(&~"method") {
428                 Some(json::String(u)) => u,
429                 _ => assume_source_method(url)
430             };
431             let key = match j.find(&~"key") {
432                 Some(json::String(u)) => Some(u),
433                 _ => None
434             };
435             let keyfp = match j.find(&~"keyfp") {
436                 Some(json::String(u)) => Some(u),
437                 _ => None
438             };
439             if method == ~"file" {
440                 url = os::make_absolute(&Path(url)).to_str();
441             }
442             return @Source {
443                 name: name,
444                 mut url: url,
445                 mut method: method,
446                 mut key: key,
447                 mut keyfp: keyfp,
448                 packages: DVec() };
449         }
450         _ => fail ~"needed dict value in source"
451     };
452 }
453
454 fn try_parse_sources(filename: &Path, sources: map::HashMap<~str, @Source>) {
455     if !os::path_exists(filename)  { return; }
456     let c = io::read_whole_file_str(filename);
457     match json::from_str(c.get()) {
458         Ok(json::Object(j)) => {
459             for j.each |k, v| {
460                 sources.insert(copy *k, parse_source(*k, v));
461                 debug!("source: %s", *k);
462             }
463         }
464         Ok(_) => fail ~"malformed sources.json",
465         Err(e) => fail fmt!("%s:%s", filename.to_str(), e.to_str())
466     }
467 }
468
469 fn load_one_source_package(src: @Source, p: &json::Object) {
470     let name = match p.find(&~"name") {
471         Some(json::String(n)) => {
472             if !valid_pkg_name(n) {
473                 warn(~"malformed source json: "
474                      + src.name + ~", '" + n + ~"'"+
475                      ~" is an invalid name (alphanumeric, underscores and" +
476                      ~" dashes only)");
477                 return;
478             }
479             n
480         }
481         _ => {
482             warn(~"malformed source json: " + src.name + ~" (missing name)");
483             return;
484         }
485     };
486
487     let uuid = match p.find(&~"uuid") {
488         Some(json::String(n)) => {
489             if !is_uuid(n) {
490                 warn(~"malformed source json: "
491                      + src.name + ~", '" + n + ~"'"+
492                      ~" is an invalid uuid");
493                 return;
494             }
495             n
496         }
497         _ => {
498             warn(~"malformed source json: " + src.name + ~" (missing uuid)");
499             return;
500         }
501     };
502
503     let url = match p.find(&~"url") {
504         Some(json::String(n)) => n,
505         _ => {
506             warn(~"malformed source json: " + src.name + ~" (missing url)");
507             return;
508         }
509     };
510
511     let method = match p.find(&~"method") {
512         Some(json::String(n)) => n,
513         _ => {
514             warn(~"malformed source json: "
515                  + src.name + ~" (missing method)");
516             return;
517         }
518     };
519
520     let reference = match p.find(&~"ref") {
521         Some(json::String(n)) => Some(n),
522         _ => None
523     };
524
525     let mut tags = ~[];
526     match p.find(&~"tags") {
527         Some(json::List(js)) => {
528           for js.each |j| {
529                 match *j {
530                     json::String(ref j) => tags.grow(1u, j),
531                     _ => ()
532                 }
533             }
534         }
535         _ => ()
536     }
537
538     let description = match p.find(&~"description") {
539         Some(json::String(n)) => n,
540         _ => {
541             warn(~"malformed source json: " + src.name
542                  + ~" (missing description)");
543             return;
544         }
545     };
546
547     let newpkg = Package {
548         name: name,
549         uuid: uuid,
550         url: url,
551         method: method,
552         description: description,
553         reference: reference,
554         tags: tags,
555         versions: ~[]
556     };
557
558     match src.packages.position(|pkg| pkg.uuid == uuid) {
559         Some(idx) => {
560             src.packages.set_elt(idx, newpkg);
561             log(debug, ~"  updated package: " + src.name + ~"/" + name);
562         }
563         None => {
564             src.packages.push(newpkg);
565         }
566     }
567
568     log(debug, ~"  loaded package: " + src.name + ~"/" + name);
569 }
570
571 fn load_source_info(c: &Cargo, src: @Source) {
572     let dir = c.sourcedir.push(src.name);
573     let srcfile = dir.push("source.json");
574     if !os::path_exists(&srcfile) { return; }
575     let srcstr = io::read_whole_file_str(&srcfile);
576     match json::from_str(srcstr.get()) {
577         Ok(ref json @ json::Object(_)) => {
578             let o = parse_source(src.name, json);
579
580             src.key = o.key;
581             src.keyfp = o.keyfp;
582         }
583         Ok(_) => {
584             warn(~"malformed source.json: " + src.name +
585                  ~"(source info is not a dict)");
586         }
587         Err(e) => {
588             warn(fmt!("%s:%s", src.name, e.to_str()));
589         }
590     };
591 }
592 fn load_source_packages(c: &Cargo, src: @Source) {
593     log(debug, ~"loading source: " + src.name);
594     let dir = c.sourcedir.push(src.name);
595     let pkgfile = dir.push("packages.json");
596     if !os::path_exists(&pkgfile) { return; }
597     let pkgstr = io::read_whole_file_str(&pkgfile);
598     match json::from_str(pkgstr.get()) {
599         Ok(json::List(js)) => {
600           for js.each |j| {
601                 match *j {
602                     json::Object(p) => {
603                         load_one_source_package(src, p);
604                     }
605                     _ => {
606                         warn(~"malformed source json: " + src.name +
607                              ~" (non-dict pkg)");
608                     }
609                 }
610             }
611         }
612         Ok(_) => {
613             warn(~"malformed packages.json: " + src.name +
614                  ~"(packages is not a list)");
615         }
616         Err(e) => {
617             warn(fmt!("%s:%s", src.name, e.to_str()));
618         }
619     };
620 }
621
622 fn build_cargo_options(argv: ~[~str]) -> Options {
623     let matches = match getopts::getopts(argv, opts()) {
624         result::Ok(m) => m,
625         result::Err(f) => {
626             fail fmt!("%s", getopts::fail_str(f));
627         }
628     };
629
630     let test = opt_present(matches, ~"test");
631     let G    = opt_present(matches, ~"G");
632     let g    = opt_present(matches, ~"g");
633     let help = opt_present(matches, ~"h") || opt_present(matches, ~"help");
634     let len  = vec::len(matches.free);
635
636     let is_install = len > 1u && matches.free[1] == ~"install";
637     let is_uninstall = len > 1u && matches.free[1] == ~"uninstall";
638
639     if G && g { fail ~"-G and -g both provided"; }
640
641     if !is_install && !is_uninstall && (g || G) {
642         fail ~"-g and -G are only valid for `install` and `uninstall|rm`";
643     }
644
645     let mode =
646         if (!is_install && !is_uninstall) || g { UserMode }
647         else if G { SystemMode }
648         else { LocalMode };
649
650     Options {test: test, mode: mode, free: matches.free, help: help}
651 }
652
653 fn configure(opts: Options) -> Cargo {
654     let home = match get_cargo_root() {
655         Ok(home) => home,
656         Err(_err) => get_cargo_sysroot().get()
657     };
658
659     let get_cargo_dir = match opts.mode {
660         SystemMode => get_cargo_sysroot,
661         UserMode => get_cargo_root,
662         LocalMode => get_cargo_root_nearest
663     };
664
665     let p = get_cargo_dir().get();
666
667     let sources = HashMap();
668     try_parse_sources(&home.push("sources.json"), sources);
669     try_parse_sources(&home.push("local-sources.json"), sources);
670
671     let dep_cache = HashMap();
672
673     let mut c = Cargo {
674         pgp: pgp::supported(),
675         root: home,
676         installdir: p,
677         bindir: p.push("bin"),
678         libdir: p.push("lib"),
679         workdir: p.push("work"),
680         sourcedir: home.push("sources"),
681         sources: sources,
682         mut current_install: ~"",
683         dep_cache: dep_cache,
684         opts: opts
685     };
686
687     need_dir(&c.root);
688     need_dir(&c.installdir);
689     need_dir(&c.sourcedir);
690     need_dir(&c.workdir);
691     need_dir(&c.libdir);
692     need_dir(&c.bindir);
693
694     for sources.each_key |k| {
695         let mut s = sources.get(k);
696         load_source_packages(&c, s);
697         sources.insert(k, s);
698     }
699
700     if c.pgp {
701         pgp::init(&c.root);
702     } else {
703         warn(~"command `gpg` was not found");
704         warn(~"you have to install gpg from source " +
705              ~" or package manager to get it to work correctly");
706     }
707
708     move c
709 }
710
711 fn for_each_package(c: &Cargo, b: fn(s: @Source, p: &Package)) {
712     for c.sources.each_value |v| {
713         for v.packages.each |p| {
714             b(v, p);
715         }
716     }
717 }
718
719 // Runs all programs in directory <buildpath>
720 fn run_programs(buildpath: &Path) {
721     let newv = os::list_dir_path(buildpath);
722     for newv.each |ct| {
723         run::run_program(ct.to_str(), ~[]);
724     }
725 }
726
727 // Runs rustc in <path + subdir> with the given flags
728 // and returns <patho + subdir>
729 fn run_in_buildpath(what: &str, path: &Path, subdir: &Path, cf: &Path,
730                     extra_flags: ~[~str]) -> Option<Path> {
731     let buildpath = path.push_rel(subdir);
732     need_dir(&buildpath);
733     debug!("%s: %s -> %s", what, cf.to_str(), buildpath.to_str());
734     let p = run::program_output(rustc_sysroot(),
735                                 ~[~"--out-dir",
736                                   buildpath.to_str(),
737                                   cf.to_str()] + extra_flags);
738     if p.status != 0 {
739         error(fmt!("rustc failed: %d\n%s\n%s", p.status, p.err, p.out));
740         return None;
741     }
742     Some(buildpath)
743 }
744
745 fn test_one_crate(_c: &Cargo, path: &Path, cf: &Path) {
746     let buildpath = match run_in_buildpath(~"testing", path,
747                                            &Path("test"),
748                                            cf,
749                                            ~[ ~"--test"]) {
750       None => return,
751     Some(bp) => bp
752   };
753   run_programs(&buildpath);
754 }
755
756 fn install_one_crate(c: &Cargo, path: &Path, cf: &Path) {
757     let buildpath = match run_in_buildpath(~"installing", path,
758                                            &Path("build"),
759                                            cf, ~[]) {
760       None => return,
761       Some(bp) => bp
762     };
763     let newv = os::list_dir_path(&buildpath);
764     let exec_suffix = os::exe_suffix();
765     for newv.each |ct| {
766         if (exec_suffix != ~"" && str::ends_with(ct.to_str(),
767                                                  exec_suffix)) ||
768             (exec_suffix == ~"" &&
769              !str::starts_with(ct.filename().get(),
770                                ~"lib")) {
771             debug!("  bin: %s", ct.to_str());
772             install_to_dir(*ct, &c.bindir);
773             if c.opts.mode == SystemMode {
774                 // FIXME (#2662): Put this file in PATH / symlink it so it can
775                 // be used as a generic executable
776                 // `cargo install -G rustray` and `rustray file.obj`
777             }
778         } else {
779             debug!("  lib: %s", ct.to_str());
780             install_to_dir(*ct, &c.libdir);
781         }
782     }
783 }
784
785
786 fn rustc_sysroot() -> ~str {
787     match os::self_exe_path() {
788         Some(path) => {
789             let rustc = path.push_many([~"..", ~"bin", ~"rustc"]);
790             debug!("  rustc: %s", rustc.to_str());
791             rustc.to_str()
792         }
793         None => ~"rustc"
794     }
795 }
796
797 fn install_source(c: &Cargo, path: &Path) {
798     debug!("source: %s", path.to_str());
799     os::change_dir(path);
800
801     let mut cratefiles = ~[];
802     for os::walk_dir(&Path(".")) |p| {
803         if p.filetype() == Some(~".rc") {
804             cratefiles.push(*p);
805         }
806     }
807
808     if vec::is_empty(cratefiles) {
809         fail ~"this doesn't look like a rust package (no .rc files)";
810     }
811
812     for cratefiles.each |cf| {
813         match load_crate(cf) {
814             None => loop,
815             Some(crate) => {
816               for crate.deps.each |query| {
817                     // FIXME (#1356): handle cyclic dependencies
818                     // (n.b. #1356 says "Cyclic dependency is an error
819                     // condition")
820
821                     let wd = get_temp_workdir(c);
822                     install_query(c, &wd, *query);
823                 }
824
825                 os::change_dir(path);
826
827                 if c.opts.test {
828                     test_one_crate(c, path, cf);
829                 }
830                 install_one_crate(c, path, cf);
831             }
832         }
833     }
834 }
835
836 fn install_git(c: &Cargo, wd: &Path, url: ~str, reference: Option<~str>) {
837     run::program_output(~"git", ~[~"clone", url, wd.to_str()]);
838     if reference.is_some() {
839         let r = reference.get();
840         os::change_dir(wd);
841         run::run_program(~"git", ~[~"checkout", r]);
842     }
843
844     install_source(c, wd);
845 }
846
847 fn install_curl(c: &Cargo, wd: &Path, url: ~str) {
848     let tarpath = wd.push("pkg.tar");
849     let p = run::program_output(~"curl", ~[~"-f", ~"-s", ~"-o",
850                                          tarpath.to_str(), url]);
851     if p.status != 0 {
852         fail fmt!("fetch of %s failed: %s", url, p.err);
853     }
854     run::run_program(~"tar", ~[~"-x", ~"--strip-components=1",
855                                ~"-C", wd.to_str(),
856                                ~"-f", tarpath.to_str()]);
857     install_source(c, wd);
858 }
859
860 fn install_file(c: &Cargo, wd: &Path, path: &Path) {
861     run::program_output(~"tar", ~[~"-x", ~"--strip-components=1",
862                                   ~"-C", wd.to_str(),
863                                   ~"-f", path.to_str()]);
864     install_source(c, wd);
865 }
866
867 fn install_package(c: &Cargo, src: ~str, wd: &Path, pkg: Package) {
868     let url = copy pkg.url;
869     let method = match pkg.method {
870         ~"git" => ~"git",
871         ~"file" => ~"file",
872         _ => ~"curl"
873     };
874
875     info(fmt!("installing %s/%s via %s...", src, pkg.name, method));
876
877     match method {
878         ~"git" => install_git(c, wd, url, copy pkg.reference),
879         ~"file" => install_file(c, wd, &Path(url)),
880         ~"curl" => install_curl(c, wd, url),
881         _ => ()
882     }
883 }
884
885 fn cargo_suggestion(c: &Cargo, fallback: fn())
886 {
887     if c.sources.size() == 0u {
888         error(~"no sources defined - you may wish to run " +
889               ~"`cargo init`");
890         return;
891     }
892     fallback();
893 }
894
895 fn install_uuid(c: &Cargo, wd: &Path, uuid: ~str) {
896     let mut ps = ~[];
897     for_each_package(c, |s, p| {
898         if p.uuid == uuid {
899             vec::push(&mut ps, (s.name, copy *p));
900         }
901     });
902     if vec::len(ps) == 1u {
903         let (sname, p) = copy ps[0];
904         install_package(c, sname, wd, p);
905         return;
906     } else if vec::len(ps) == 0u {
907         cargo_suggestion(c, || {
908             error(~"can't find package: " + uuid);
909         });
910         return;
911     }
912     error(~"found multiple packages:");
913     for ps.each |elt| {
914         let (sname,p) = copy *elt;
915         info(~"  " + sname + ~"/" + p.uuid + ~" (" + p.name + ~")");
916     }
917 }
918
919 fn install_named(c: &Cargo, wd: &Path, name: ~str) {
920     let mut ps = ~[];
921     for_each_package(c, |s, p| {
922         if p.name == name {
923             vec::push(&mut ps, (s.name, copy *p));
924         }
925     });
926     if vec::len(ps) == 1u {
927         let (sname, p) = copy ps[0];
928         install_package(c, sname, wd, p);
929         return;
930     } else if vec::len(ps) == 0u {
931         cargo_suggestion(c, || {
932             error(~"can't find package: " + name);
933         });
934         return;
935     }
936     error(~"found multiple packages:");
937     for ps.each |elt| {
938         let (sname,p) = copy *elt;
939         info(~"  " + sname + ~"/" + p.uuid + ~" (" + p.name + ~")");
940     }
941 }
942
943 fn install_uuid_specific(c: &Cargo, wd: &Path, src: ~str, uuid: ~str) {
944     match c.sources.find(src) {
945         Some(s) => {
946             for s.packages.each |p| {
947                 if p.uuid == uuid {
948                     install_package(c, src, wd, *p);
949                     return;
950                 }
951             }
952         }
953         _ => ()
954     }
955     error(~"can't find package: " + src + ~"/" + uuid);
956 }
957
958 fn install_named_specific(c: &Cargo, wd: &Path, src: ~str, name: ~str) {
959     match c.sources.find(src) {
960         Some(s) => {
961             for s.packages.each |p| {
962                 if p.name == name {
963                     install_package(c, src, wd, *p);
964                     return;
965                 }
966             }
967         }
968         _ => ()
969     }
970     error(~"can't find package: " + src + ~"/" + name);
971 }
972
973 fn cmd_uninstall(c: &Cargo) {
974     if vec::len(c.opts.free) < 3u {
975         cmd_usage();
976         return;
977     }
978
979     let lib = &c.libdir;
980     let bin = &c.bindir;
981     let target = c.opts.free[2u];
982
983     // FIXME (#2662): needs stronger pattern matching
984     // FIXME (#2662): needs to uninstall from a specified location in a
985     // cache instead of looking for it (binaries can be uninstalled by
986     // name only)
987
988     fn try_uninstall(p: &Path) -> bool {
989         if os::remove_file(p) {
990             info(~"uninstalled: '" + p.to_str() + ~"'");
991             true
992         } else {
993             error(~"could not uninstall: '" +
994                   p.to_str() + ~"'");
995             false
996         }
997     }
998
999     if is_uuid(target) {
1000         for os::list_dir(lib).each |file| {
1001             match str::find_str(*file, ~"-" + target + ~"-") {
1002               Some(_) => if !try_uninstall(&lib.push(*file)) { return },
1003               None => ()
1004             }
1005         }
1006         error(~"can't find package with uuid: " + target);
1007     } else {
1008         for os::list_dir(lib).each |file| {
1009             match str::find_str(*file, ~"lib" + target + ~"-") {
1010               Some(_) => if !try_uninstall(&lib.push(*file)) { return },
1011               None => ()
1012             }
1013         }
1014         for os::list_dir(bin).each |file| {
1015             match str::find_str(*file, target) {
1016               Some(_) => if !try_uninstall(&lib.push(*file)) { return },
1017               None => ()
1018             }
1019         }
1020
1021         error(~"can't find package with name: " + target);
1022     }
1023 }
1024
1025 fn install_query(c: &Cargo, wd: &Path, target: ~str) {
1026     match c.dep_cache.find(target) {
1027         Some(inst) => {
1028             if inst {
1029                 return;
1030             }
1031         }
1032         None => ()
1033     }
1034
1035     c.dep_cache.insert(target, true);
1036
1037     if is_archive_path(target) {
1038         install_file(c, wd, &Path(target));
1039         return;
1040     } else if is_git_url(target) {
1041         let reference = if c.opts.free.len() >= 4u {
1042             Some(c.opts.free[3u])
1043         } else {
1044             None
1045         };
1046         install_git(c, wd, target, reference);
1047     } else if !valid_pkg_name(target) && has_archive_extension(target) {
1048         install_curl(c, wd, target);
1049         return;
1050     } else {
1051         let mut ps = copy target;
1052
1053         match str::find_char(ps, '/') {
1054             option::Some(idx) => {
1055                 let source = str::slice(ps, 0u, idx);
1056                 ps = str::slice(ps, idx + 1u, str::len(ps));
1057                 if is_uuid(ps) {
1058                     install_uuid_specific(c, wd, source, ps);
1059                 } else {
1060                     install_named_specific(c, wd, source, ps);
1061                 }
1062             }
1063             option::None => {
1064                 if is_uuid(ps) {
1065                     install_uuid(c, wd, ps);
1066                 } else {
1067                     install_named(c, wd, ps);
1068                 }
1069             }
1070         }
1071     }
1072
1073     // FIXME (#2662): This whole dep_cache and current_install thing is
1074     // a bit of a hack. It should be cleaned up in the future.
1075
1076     if target == c.current_install {
1077         for c.dep_cache.each |k, _v| {
1078             c.dep_cache.remove(k);
1079         }
1080
1081         c.current_install = ~"";
1082     }
1083 }
1084
1085 fn get_temp_workdir(c: &Cargo) -> Path {
1086     match tempfile::mkdtemp(&c.workdir, "cargo") {
1087       Some(wd) => wd,
1088       None => fail fmt!("needed temp dir: %s",
1089                         c.workdir.to_str())
1090     }
1091 }
1092
1093 fn cmd_install(c: &Cargo) unsafe {
1094     let wd = get_temp_workdir(c);
1095
1096     if vec::len(c.opts.free) == 2u {
1097         let cwd = os::getcwd();
1098         let status = run::run_program(~"cp", ~[~"-R", cwd.to_str(),
1099                                                wd.to_str()]);
1100
1101         if status != 0 {
1102             fail fmt!("could not copy directory: %s", cwd.to_str());
1103         }
1104
1105         install_source(c, &wd);
1106         return;
1107     }
1108
1109     sync(c);
1110
1111     let query = c.opts.free[2];
1112     c.current_install = query.to_str();
1113
1114     install_query(c, &wd, query);
1115 }
1116
1117 fn sync(c: &Cargo) {
1118     for c.sources.each_key |k| {
1119         let mut s = c.sources.get(k);
1120         sync_one(c, s);
1121         c.sources.insert(k, s);
1122     }
1123 }
1124
1125 fn sync_one_file(c: &Cargo, dir: &Path, src: @Source) -> bool {
1126     let name = src.name;
1127     let srcfile = dir.push("source.json.new");
1128     let destsrcfile = dir.push("source.json");
1129     let pkgfile = dir.push("packages.json.new");
1130     let destpkgfile = dir.push("packages.json");
1131     let keyfile = dir.push("key.gpg");
1132     let srcsigfile = dir.push("source.json.sig");
1133     let sigfile = dir.push("packages.json.sig");
1134     let url = Path(src.url);
1135     let mut has_src_file = false;
1136
1137     if !os::copy_file(&url.push("packages.json"), &pkgfile) {
1138         error(fmt!("fetch for source %s (url %s) failed",
1139                    name, url.to_str()));
1140         return false;
1141     }
1142
1143     if os::copy_file(&url.push("source.json"), &srcfile) {
1144         has_src_file = false;
1145     }
1146
1147     os::copy_file(&url.push("source.json.sig"), &srcsigfile);
1148     os::copy_file(&url.push("packages.json.sig"), &sigfile);
1149
1150     match copy src.key {
1151         Some(u) => {
1152             let p = run::program_output(~"curl",
1153                                         ~[~"-f", ~"-s",
1154                                           ~"-o", keyfile.to_str(), u]);
1155             if p.status != 0 {
1156                 error(fmt!("fetch for source %s (key %s) failed", name, u));
1157                 return false;
1158             }
1159             pgp::add(&c.root, &keyfile);
1160         }
1161         _ => ()
1162     }
1163     match (src.key, src.keyfp) {
1164         (Some(_), Some(f)) => {
1165             let r = pgp::verify(&c.root, &pkgfile, &sigfile);
1166
1167             if !r {
1168                 error(fmt!("signature verification failed for source %s with \
1169                             key %s", name, f));
1170                 return false;
1171             }
1172
1173             if has_src_file {
1174                 let e = pgp::verify(&c.root, &srcfile, &srcsigfile);
1175
1176                 if !e {
1177                     error(fmt!("signature verification failed for source %s \
1178                                 with key %s", name, f));
1179                     return false;
1180                 }
1181             }
1182         }
1183         _ => ()
1184     }
1185
1186     copy_warn(&pkgfile, &destpkgfile);
1187
1188     if has_src_file {
1189         copy_warn(&srcfile, &destsrcfile);
1190     }
1191
1192     os::remove_file(&keyfile);
1193     os::remove_file(&srcfile);
1194     os::remove_file(&srcsigfile);
1195     os::remove_file(&pkgfile);
1196     os::remove_file(&sigfile);
1197
1198     info(fmt!("synced source: %s", name));
1199
1200     return true;
1201 }
1202
1203 fn sync_one_git(c: &Cargo, dir: &Path, src: @Source) -> bool {
1204     let name = src.name;
1205     let srcfile = dir.push("source.json");
1206     let pkgfile = dir.push("packages.json");
1207     let keyfile = dir.push("key.gpg");
1208     let srcsigfile = dir.push("source.json.sig");
1209     let sigfile = dir.push("packages.json.sig");
1210     let url = src.url;
1211
1212     fn rollback(name: ~str, dir: &Path, insecure: bool) {
1213         fn msg(name: ~str, insecure: bool) {
1214             error(fmt!("could not rollback source: %s", name));
1215
1216             if insecure {
1217                 warn(~"a past security check failed on source " +
1218                      name + ~" and rolling back the source failed -"
1219                      + ~" this source may be compromised");
1220             }
1221         }
1222
1223         if !os::change_dir(dir) {
1224             msg(name, insecure);
1225         }
1226         else {
1227             let p = run::program_output(~"git", ~[~"reset", ~"--hard",
1228                                                 ~"HEAD@{1}"]);
1229
1230             if p.status != 0 {
1231                 msg(name, insecure);
1232             }
1233         }
1234     }
1235
1236     if !os::path_exists(&dir.push(".git")) {
1237         let p = run::program_output(~"git", ~[~"clone", url, dir.to_str()]);
1238
1239         if p.status != 0 {
1240             error(fmt!("fetch for source %s (url %s) failed", name, url));
1241             return false;
1242         }
1243     }
1244     else {
1245         if !os::change_dir(dir) {
1246             error(fmt!("fetch for source %s (url %s) failed", name, url));
1247             return false;
1248         }
1249
1250         let p = run::program_output(~"git", ~[~"pull"]);
1251
1252         if p.status != 0 {
1253             error(fmt!("fetch for source %s (url %s) failed", name, url));
1254             return false;
1255         }
1256     }
1257
1258     let has_src_file = os::path_exists(&srcfile);
1259
1260     match copy src.key {
1261         Some(u) => {
1262             let p = run::program_output(~"curl",
1263                                         ~[~"-f", ~"-s",
1264                                           ~"-o", keyfile.to_str(), u]);
1265             if p.status != 0 {
1266                 error(fmt!("fetch for source %s (key %s) failed", name, u));
1267                 rollback(name, dir, false);
1268                 return false;
1269             }
1270             pgp::add(&c.root, &keyfile);
1271         }
1272         _ => ()
1273     }
1274     match (src.key, src.keyfp) {
1275         (Some(_), Some(f)) => {
1276             let r = pgp::verify(&c.root, &pkgfile, &sigfile);
1277
1278             if !r {
1279                 error(fmt!("signature verification failed for source %s with \
1280                             key %s", name, f));
1281                 rollback(name, dir, false);
1282                 return false;
1283             }
1284
1285             if has_src_file {
1286                 let e = pgp::verify(&c.root, &srcfile, &srcsigfile);
1287
1288                 if !e {
1289                     error(fmt!("signature verification failed for source %s \
1290                                 with key %s", name, f));
1291                     rollback(name, dir, false);
1292                     return false;
1293                 }
1294             }
1295         }
1296         _ => ()
1297     }
1298
1299     os::remove_file(&keyfile);
1300
1301     info(fmt!("synced source: %s", name));
1302
1303     return true;
1304 }
1305
1306 fn sync_one_curl(c: &Cargo, dir: &Path, src: @Source) -> bool {
1307     let name = src.name;
1308     let srcfile = dir.push("source.json.new");
1309     let destsrcfile = dir.push("source.json");
1310     let pkgfile = dir.push("packages.json.new");
1311     let destpkgfile = dir.push("packages.json");
1312     let keyfile = dir.push("key.gpg");
1313     let srcsigfile = dir.push("source.json.sig");
1314     let sigfile = dir.push("packages.json.sig");
1315     let mut url = src.url;
1316     let smart = !str::ends_with(src.url, ~"packages.json");
1317     let mut has_src_file = false;
1318
1319     if smart {
1320         url += ~"/packages.json";
1321     }
1322
1323     let p = run::program_output(~"curl",
1324                                 ~[~"-f", ~"-s",
1325                                   ~"-o", pkgfile.to_str(), url]);
1326
1327     if p.status != 0 {
1328         error(fmt!("fetch for source %s (url %s) failed", name, url));
1329         return false;
1330     }
1331     if smart {
1332         url = src.url + ~"/source.json";
1333         let p =
1334             run::program_output(~"curl",
1335                                 ~[~"-f", ~"-s",
1336                                   ~"-o", srcfile.to_str(), url]);
1337
1338         if p.status == 0 {
1339             has_src_file = true;
1340         }
1341     }
1342
1343     match copy src.key {
1344        Some(u) => {
1345             let p = run::program_output(~"curl",
1346                                         ~[~"-f", ~"-s",
1347                                           ~"-o", keyfile.to_str(), u]);
1348             if p.status != 0 {
1349                 error(fmt!("fetch for source %s (key %s) failed", name, u));
1350                 return false;
1351             }
1352             pgp::add(&c.root, &keyfile);
1353         }
1354         _ => ()
1355     }
1356     match (src.key, src.keyfp) {
1357         (Some(_), Some(f)) => {
1358             if smart {
1359                 url = src.url + ~"/packages.json.sig";
1360             }
1361             else {
1362                 url = src.url + ~".sig";
1363             }
1364
1365             let mut p = run::program_output(~"curl",
1366                                             ~[~"-f", ~"-s", ~"-o",
1367                                               sigfile.to_str(), url]);
1368             if p.status != 0 {
1369                 error(fmt!("fetch for source %s (sig %s) failed", name, url));
1370                 return false;
1371             }
1372
1373             let r = pgp::verify(&c.root, &pkgfile, &sigfile);
1374
1375             if !r {
1376                 error(fmt!("signature verification failed for source %s with \
1377                             key %s", name, f));
1378                 return false;
1379             }
1380
1381             if smart && has_src_file {
1382                 url = src.url + ~"/source.json.sig";
1383
1384                 p = run::program_output(~"curl",
1385                                         ~[~"-f", ~"-s", ~"-o",
1386                                           srcsigfile.to_str(), url]);
1387                 if p.status != 0 {
1388                     error(fmt!("fetch for source %s (sig %s) failed",
1389                           name, url));
1390                     return false;
1391                 }
1392
1393                 let e = pgp::verify(&c.root, &srcfile, &srcsigfile);
1394
1395                 if !e {
1396                     error(~"signature verification failed for " +
1397                           ~"source " + name + ~" with key " + f);
1398                     return false;
1399                 }
1400             }
1401         }
1402         _ => ()
1403     }
1404
1405     copy_warn(&pkgfile, &destpkgfile);
1406
1407     if smart && has_src_file {
1408         copy_warn(&srcfile, &destsrcfile);
1409     }
1410
1411     os::remove_file(&keyfile);
1412     os::remove_file(&srcfile);
1413     os::remove_file(&srcsigfile);
1414     os::remove_file(&pkgfile);
1415     os::remove_file(&sigfile);
1416
1417     info(fmt!("synced source: %s", name));
1418
1419     return true;
1420 }
1421
1422 fn sync_one(c: &Cargo, src: @Source) {
1423     let name = src.name;
1424     let dir = c.sourcedir.push(name);
1425
1426     info(fmt!("syncing source: %s...", name));
1427
1428     need_dir(&dir);
1429
1430     let result = match src.method {
1431         ~"git" => sync_one_git(c, &dir, src),
1432         ~"file" => sync_one_file(c, &dir, src),
1433         _ => sync_one_curl(c, &dir, src)
1434     };
1435
1436     if result {
1437         load_source_info(c, src);
1438         load_source_packages(c, src);
1439     }
1440 }
1441
1442 fn cmd_init(c: &Cargo) {
1443     let srcurl = ~"http://www.rust-lang.org/cargo/sources.json";
1444     let sigurl = ~"http://www.rust-lang.org/cargo/sources.json.sig";
1445
1446     let srcfile = c.root.push("sources.json.new");
1447     let sigfile = c.root.push("sources.json.sig");
1448     let destsrcfile = c.root.push("sources.json");
1449
1450     let p =
1451         run::program_output(~"curl", ~[~"-f", ~"-s",
1452                                        ~"-o", srcfile.to_str(), srcurl]);
1453     if p.status != 0 {
1454         error(fmt!("fetch of sources.json failed: %s", p.out));
1455         return;
1456     }
1457
1458     let p =
1459         run::program_output(~"curl", ~[~"-f", ~"-s",
1460                                        ~"-o", sigfile.to_str(), sigurl]);
1461     if p.status != 0 {
1462         error(fmt!("fetch of sources.json.sig failed: %s", p.out));
1463         return;
1464     }
1465
1466     let r = pgp::verify(&c.root, &srcfile, &sigfile);
1467     if !r {
1468         error(fmt!("signature verification failed for '%s'",
1469                    srcfile.to_str()));
1470         return;
1471     }
1472
1473     copy_warn(&srcfile, &destsrcfile);
1474     os::remove_file(&srcfile);
1475     os::remove_file(&sigfile);
1476
1477     info(fmt!("initialized .cargo in %s", c.root.to_str()));
1478 }
1479
1480 fn print_pkg(s: @Source, p: &Package) {
1481     let mut m = s.name + ~"/" + p.name + ~" (" + p.uuid + ~")";
1482     if vec::len(p.tags) > 0u {
1483         m = m + ~" [" + str::connect(p.tags, ~", ") + ~"]";
1484     }
1485     info(m);
1486     if p.description != ~"" {
1487         print(~"   >> " + p.description + ~"\n")
1488     }
1489 }
1490
1491 fn print_source(s: @Source) {
1492     info(s.name + ~" (" + s.url + ~")");
1493
1494     let pks = sort::merge_sort(s.packages.get(), sys::shape_lt);
1495     let l = vec::len(pks);
1496
1497     print(io::with_str_writer(|writer| {
1498         let mut list = ~"   >> ";
1499
1500         for vec::eachi(pks) |i, pk| {
1501             if str::len(list) > 78u {
1502                 writer.write_line(list);
1503                 list = ~"   >> ";
1504             }
1505             list += pk.name + (if l - 1u == i { ~"" } else { ~", " });
1506         }
1507
1508         writer.write_line(list);
1509     }));
1510 }
1511
1512 fn cmd_list(c: &Cargo) {
1513     sync(c);
1514
1515     if vec::len(c.opts.free) >= 3u {
1516         let v = vec::view(c.opts.free, 2u, vec::len(c.opts.free));
1517         for vec::each(v) |name| {
1518             if !valid_pkg_name(*name) {
1519                 error(fmt!("'%s' is an invalid source name", *name));
1520             } else {
1521                 match c.sources.find(*name) {
1522                     Some(source) => {
1523                         print_source(source);
1524                     }
1525                     None => {
1526                         error(fmt!("no such source: %s", *name));
1527                     }
1528                 }
1529             }
1530         }
1531     } else {
1532         for c.sources.each_value |v| {
1533             print_source(v);
1534         }
1535     }
1536 }
1537
1538 fn cmd_search(c: &Cargo) {
1539     if vec::len(c.opts.free) < 3u {
1540         cmd_usage();
1541         return;
1542     }
1543
1544     sync(c);
1545
1546     let mut n = 0;
1547     let name = c.opts.free[2];
1548     let tags = vec::slice(c.opts.free, 3u, vec::len(c.opts.free));
1549     for_each_package(c, |s, p| {
1550         if (str::contains(p.name, name) || name == ~"*") &&
1551             vec::all(tags, |t| vec::contains(p.tags, t) ) {
1552             print_pkg(s, p);
1553             n += 1;
1554         }
1555     });
1556     info(fmt!("found %d packages", n));
1557 }
1558
1559 fn install_to_dir(srcfile: &Path, destdir: &Path) {
1560     let newfile = destdir.push(srcfile.filename().get());
1561
1562     let status = run::run_program(~"cp", ~[~"-r", srcfile.to_str(),
1563                                            newfile.to_str()]);
1564     if status == 0 {
1565         info(fmt!("installed: '%s'", newfile.to_str()));
1566     } else {
1567         error(fmt!("could not install: '%s'", newfile.to_str()));
1568     }
1569 }
1570
1571 fn dump_cache(c: &Cargo) {
1572     need_dir(&c.root);
1573
1574     let out = c.root.push("cache.json");
1575     let _root = json::Object(~LinearMap());
1576
1577     if os::path_exists(&out) {
1578         copy_warn(&out, &c.root.push("cache.json.old"));
1579     }
1580 }
1581 fn dump_sources(c: &Cargo) {
1582     if c.sources.size() < 1u {
1583         return;
1584     }
1585
1586     need_dir(&c.root);
1587
1588     let out = c.root.push("sources.json");
1589
1590     if os::path_exists(&out) {
1591         copy_warn(&out, &c.root.push("sources.json.old"));
1592     }
1593
1594     match io::buffered_file_writer(&out) {
1595         result::Ok(writer) => {
1596             let mut hash = ~LinearMap();
1597
1598             for c.sources.each |k, v| {
1599                 let mut chash = ~LinearMap();
1600
1601                 chash.insert(~"url", json::String(v.url));
1602                 chash.insert(~"method", json::String(v.method));
1603
1604                 match copy v.key {
1605                     Some(key) => {
1606                         chash.insert(~"key", json::String(copy key));
1607                     }
1608                     _ => ()
1609                 }
1610                 match copy v.keyfp {
1611                     Some(keyfp) => {
1612                         chash.insert(~"keyfp", json::String(copy keyfp));
1613                     }
1614                     _ => ()
1615                 }
1616
1617                 hash.insert(copy k, json::Object(move chash));
1618             }
1619
1620             json::to_writer(writer, &json::Object(move hash))
1621         }
1622         result::Err(e) => {
1623             error(fmt!("could not dump sources: %s", e));
1624         }
1625     }
1626 }
1627
1628 fn copy_warn(srcfile: &Path, destfile: &Path) {
1629     if !os::copy_file(srcfile, destfile) {
1630         warn(fmt!("copying %s to %s failed",
1631                   srcfile.to_str(), destfile.to_str()));
1632     }
1633 }
1634
1635 fn cmd_sources(c: &Cargo) {
1636     if vec::len(c.opts.free) < 3u {
1637         for c.sources.each_value |v| {
1638             info(fmt!("%s (%s) via %s",
1639                       v.name, v.url, v.method));
1640         }
1641         return;
1642     }
1643
1644     let action = c.opts.free[2u];
1645
1646     match action {
1647         ~"clear" => {
1648           for c.sources.each_key |k| {
1649                 c.sources.remove(k);
1650             }
1651
1652             info(~"cleared sources");
1653         }
1654         ~"add" => {
1655             if vec::len(c.opts.free) < 5u {
1656                 cmd_usage();
1657                 return;
1658             }
1659
1660             let name = c.opts.free[3u];
1661             let url = c.opts.free[4u];
1662
1663             if !valid_pkg_name(name) {
1664                 error(fmt!("'%s' is an invalid source name", name));
1665                 return;
1666             }
1667
1668             if c.sources.contains_key(name) {
1669                 error(fmt!("source already exists: %s", name));
1670             } else {
1671                 c.sources.insert(name, @Source {
1672                     name: name,
1673                     mut url: url,
1674                     mut method: assume_source_method(url),
1675                     mut key: None,
1676                     mut keyfp: None,
1677                     packages: DVec()
1678                 });
1679                 info(fmt!("added source: %s", name));
1680             }
1681         }
1682         ~"remove" => {
1683             if vec::len(c.opts.free) < 4u {
1684                 cmd_usage();
1685                 return;
1686             }
1687
1688             let name = c.opts.free[3u];
1689
1690             if !valid_pkg_name(name) {
1691                 error(fmt!("'%s' is an invalid source name", name));
1692                 return;
1693             }
1694
1695             if c.sources.contains_key(name) {
1696                 c.sources.remove(name);
1697                 info(fmt!("removed source: %s", name));
1698             } else {
1699                 error(fmt!("no such source: %s", name));
1700             }
1701         }
1702         ~"set-url" => {
1703             if vec::len(c.opts.free) < 5u {
1704                 cmd_usage();
1705                 return;
1706             }
1707
1708             let name = c.opts.free[3u];
1709             let url = c.opts.free[4u];
1710
1711             if !valid_pkg_name(name) {
1712                 error(fmt!("'%s' is an invalid source name", name));
1713                 return;
1714             }
1715
1716             match c.sources.find(name) {
1717                 Some(source) => {
1718                     let old = copy source.url;
1719                     let method = assume_source_method(url);
1720
1721                     source.url = url;
1722                     source.method = method;
1723
1724                     c.sources.insert(name, source);
1725
1726                     info(fmt!("changed source url: '%s' to '%s'", old, url));
1727                 }
1728                 None => {
1729                     error(fmt!("no such source: %s", name));
1730                 }
1731             }
1732         }
1733         ~"set-method" => {
1734             if vec::len(c.opts.free) < 5u {
1735                 cmd_usage();
1736                 return;
1737             }
1738
1739             let name = c.opts.free[3u];
1740             let method = c.opts.free[4u];
1741
1742             if !valid_pkg_name(name) {
1743                 error(fmt!("'%s' is an invalid source name", name));
1744                 return;
1745             }
1746
1747             match c.sources.find(name) {
1748                 Some(source) => {
1749                     let old = copy source.method;
1750
1751                     source.method = match method {
1752                         ~"git" => ~"git",
1753                         ~"file" => ~"file",
1754                         _ => ~"curl"
1755                     };
1756
1757                     c.sources.insert(name, source);
1758
1759                     info(fmt!("changed source method: '%s' to '%s'", old,
1760                          method));
1761                 }
1762                 None => {
1763                     error(fmt!("no such source: %s", name));
1764                 }
1765             }
1766         }
1767         ~"rename" => {
1768             if vec::len(c.opts.free) < 5u {
1769                 cmd_usage();
1770                 return;
1771             }
1772
1773             let name = c.opts.free[3u];
1774             let newn = c.opts.free[4u];
1775
1776             if !valid_pkg_name(name) {
1777                 error(fmt!("'%s' is an invalid source name", name));
1778                 return;
1779             }
1780             if !valid_pkg_name(newn) {
1781                 error(fmt!("'%s' is an invalid source name", newn));
1782                 return;
1783             }
1784
1785             match c.sources.find(name) {
1786                 Some(source) => {
1787                     c.sources.remove(name);
1788                     c.sources.insert(newn, source);
1789                     info(fmt!("renamed source: %s to %s", name, newn));
1790                 }
1791                 None => {
1792                     error(fmt!("no such source: %s", name));
1793                 }
1794             }
1795         }
1796         _ => cmd_usage()
1797     }
1798 }
1799
1800 fn cmd_usage() {
1801     print(~"Usage: cargo <cmd> [options] [args..]
1802 e.g. cargo install <name>
1803
1804 Where <cmd> is one of:
1805     init, install, list, search, sources,
1806     uninstall, usage
1807
1808 Options:
1809
1810     -h, --help                  Display this message
1811     <cmd> -h, <cmd> --help      Display help for <cmd>
1812 ");
1813 }
1814
1815 fn cmd_usage_init() {
1816     print(~"cargo init
1817
1818 Re-initialize cargo in ~/.cargo. Clears all sources and then adds the
1819 default sources from <www.rust-lang.org/sources.json>.");
1820 }
1821
1822 fn cmd_usage_install() {
1823     print(~"cargo install
1824 cargo install [source/]<name>[@version]
1825 cargo install [source/]<uuid>[@version]
1826 cargo install <git url> [ref]
1827 cargo install <tarball url>
1828 cargo install <tarball file>
1829
1830 Options:
1831     --test      Run crate tests before installing
1832     -g          Install to the user level (~/.cargo/bin/ instead of
1833                 locally in ./.cargo/bin/ by default)
1834     -G          Install to the system level (/usr/local/lib/cargo/bin/)
1835
1836 Install a crate. If no arguments are supplied, it installs from
1837 the current working directory. If a source is provided, only install
1838 from that source, otherwise it installs from any source.");
1839 }
1840
1841 fn cmd_usage_uninstall() {
1842     print(~"cargo uninstall [source/]<name>[@version]
1843 cargo uninstall [source/]<uuid>[@version]
1844 cargo uninstall <meta-name>[@version]
1845 cargo uninstall <meta-uuid>[@version]
1846
1847 Options:
1848     -g          Remove from the user level (~/.cargo/bin/ instead of
1849                 locally in ./.cargo/bin/ by default)
1850     -G          Remove from the system level (/usr/local/lib/cargo/bin/)
1851
1852 Remove a crate. If a source is provided, only remove
1853 from that source, otherwise it removes from any source.
1854 If a crate was installed directly (git, tarball, etc.), you can remove
1855 it by metadata.");
1856 }
1857
1858 fn cmd_usage_list() {
1859     print(~"cargo list [sources..]
1860
1861 If no arguments are provided, list all sources and their packages.
1862 If source names are provided, list those sources and their packages.
1863 ");
1864 }
1865
1866 fn cmd_usage_search() {
1867     print(~"cargo search <query | '*'> [tags..]
1868
1869 Search packages.");
1870 }
1871
1872 fn cmd_usage_sources() {
1873     print(~"cargo sources
1874 cargo sources add <name> <url>
1875 cargo sources remove <name>
1876 cargo sources rename <name> <new>
1877 cargo sources set-url <name> <url>
1878 cargo sources set-method <name> <method>
1879
1880 If no arguments are supplied, list all sources (but not their packages).
1881
1882 Commands:
1883     add             Add a source. The source method will be guessed
1884                     from the URL.
1885     remove          Remove a source.
1886     rename          Rename a source.
1887     set-url         Change the URL for a source.
1888     set-method      Change the method for a source.");
1889 }
1890
1891 fn main() {
1892     let argv = os::args();
1893     let o = build_cargo_options(argv);
1894
1895     if vec::len(o.free) < 2u {
1896         cmd_usage();
1897         return;
1898     }
1899     if o.help {
1900         match o.free[1] {
1901             ~"init" => cmd_usage_init(),
1902             ~"install" => cmd_usage_install(),
1903             ~"uninstall" => cmd_usage_uninstall(),
1904             ~"list" => cmd_usage_list(),
1905             ~"search" => cmd_usage_search(),
1906             ~"sources" => cmd_usage_sources(),
1907             _ => cmd_usage()
1908         }
1909         return;
1910     }
1911     if o.free[1] == ~"usage" {
1912         cmd_usage();
1913         return;
1914     }
1915
1916     let mut c = configure(o);
1917     let home = c.root;
1918     let first_time = os::path_exists(&home.push("sources.json"));
1919
1920     if !first_time && o.free[1] != ~"init" {
1921         cmd_init(&c);
1922
1923         // FIXME (#2662): shouldn't need to reconfigure
1924         c = configure(o);
1925     }
1926
1927     let c = &move c;
1928
1929     match o.free[1] {
1930         ~"init" => cmd_init(c),
1931         ~"install" => cmd_install(c),
1932         ~"uninstall" => cmd_uninstall(c),
1933         ~"list" => cmd_list(c),
1934         ~"search" => cmd_search(c),
1935         ~"sources" => cmd_sources(c),
1936         _ => cmd_usage()
1937     }
1938
1939     dump_cache(c);
1940     dump_sources(c);
1941 }