1 // cargo.rs - Rust package manager
5 use syntax::{ast, codemap, parse, visit, attr};
6 use syntax::diagnostic::span_handler;
8 use rustc::metadata::filesearch::{get_cargo_root, get_cargo_root_nearest,
9 get_cargo_sysroot, libdir};
10 use syntax::diagnostic;
12 use result::{Ok, Err};
14 use send_map::linear::LinearMap;
15 use std::{map, json, tempfile, term, sort, getopts};
18 use getopts::{optflag, optopt, opt_present};
27 reference: Option<~str>,
29 versions: ~[(~str, ~str)]
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; }
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) }
58 mut key: Option<~str>,
59 mut keyfp: Option<~str>,
60 packages: DVec<Package>
71 sources: map::HashMap<~str, @Source>,
72 mut current_install: ~str,
73 dep_cache: map::HashMap<~str, bool>,
83 crate_type: Option<~str>,
94 enum Mode { SystemMode, UserMode, LocalMode }
97 pure fn eq(other: &Mode) -> bool {
98 (self as uint) == ((*other) as uint)
100 pure fn ne(other: &Mode) -> bool { !self.eq(other) }
103 fn opts() -> ~[getopts::Opt] {
104 ~[optflag(~"g"), optflag(~"G"), optflag(~"test"),
105 optflag(~"h"), optflag(~"help")]
109 let out = io::stdout();
111 if term::color_supported() {
112 term::fg(out, term::color_green);
113 out.write_str(~"info: ");
116 } else { out.write_line(~"info: " + msg); }
120 let out = io::stdout();
122 if term::color_supported() {
123 term::fg(out, term::color_yellow);
124 out.write_str(~"warning: ");
127 }else { out.write_line(~"warning: " + msg); }
130 fn error(msg: ~str) {
131 let out = io::stdout();
133 if term::color_supported() {
134 term::fg(out, term::color_red);
135 out.write_str(~"error: ");
139 else { out.write_line(~"error: " + msg); }
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')
153 if !part.all(is_hex_digit) {
159 if part.len() == 8u {
164 if part.len() == 4u {
169 if part.len() == 12u {
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ป");
194 // FIXME (#2661): implement url/URL parsing so we don't have to resort
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")
213 fn is_archive_path(u: ~str) -> bool {
214 has_archive_extension(u) && os::path_exists(&Path(u))
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
221 match str::find_str(u, ~"://") {
222 option::Some(_) => has_archive_extension(u),
227 fn is_git_url(url: ~str) -> bool {
228 if str::ends_with(url, ~"/") { str::ends_with(url, ~".git/") }
230 str::starts_with(url, ~"git://") || str::ends_with(url, ~".git")
234 fn assume_source_method(url: ~str) -> ~str {
238 if str::starts_with(url, ~"file://") || os::path_exists(&Path(url)) {
245 fn load_link(mis: ~[@ast::meta_item]) -> (Option<~str>,
253 ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) => {
255 ~"name" => name = Some(*s),
256 ~"vers" => vers = Some(*s),
257 ~"uuid" => uuid = Some(*s),
261 _ => fail ~"load_link: meta items must be name-values"
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);
276 let mut crate_type = None;
278 for c.node.attrs.each |a| {
279 match a.node.value.node {
280 ast::meta_name_value(v, {node: ast::lit_str(_), span: _}) => {
282 ~"desc" => desc = Some(v),
283 ~"sigs" => sigs = Some(v),
284 ~"crate_type" => crate_type = Some(v),
288 ast::meta_list(v, mis) => {
290 let (n, v, u) = load_link(mis);
297 fail ~"crate attributes may not contain " +
307 fn goto_view_item(ps: syntax::parse::parse_sess, e: env,
308 i: @ast::view_item) {
310 ast::view_item_use(ident, metas, _) => {
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))]
319 let mut attr_name = ident;
320 let mut attr_vers = ~"";
321 let mut attr_from = ~"";
324 match attr::get_meta_item_value_str(*item) {
326 let name = attr::get_meta_item_name(*item);
329 ~"vers" => attr_vers = value,
330 ~"from" => attr_from = value,
338 let query = if !str::is_empty(attr_from) {
341 if !str::is_empty(attr_vers) {
342 ps.interner.get(attr_name) + ~"@" + attr_vers
343 } else { *ps.interner.get(attr_name) }
346 match *ps.interner.get(attr_name) {
347 ~"std" | ~"core" => (),
348 _ => e.deps.push(query)
354 fn goto_item(_e: env, _i: @ast::item) {
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()
366 visit::visit_crate(*c, (), v);
368 let deps = copy e.deps;
370 match (name, vers, uuid) {
371 (Some(name0), Some(vers0), Some(uuid0)) => {
378 crate_type: crate_type,
386 io::stdout().write_line(s);
389 fn rest(s: ~str, start: uint) -> ~str {
390 if (start >= str::len(s)) {
393 str::slice(s, start, str::len(s))
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());
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') ||
413 s.all(is_valid_digit)
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);
423 let mut url = match j.find(&~"url") {
424 Some(json::String(u)) => u,
425 _ => fail ~"needed 'url' field in source"
427 let method = match j.find(&~"method") {
428 Some(json::String(u)) => u,
429 _ => assume_source_method(url)
431 let key = match j.find(&~"key") {
432 Some(json::String(u)) => Some(u),
435 let keyfp = match j.find(&~"keyfp") {
436 Some(json::String(u)) => Some(u),
439 if method == ~"file" {
440 url = os::make_absolute(&Path(url)).to_str();
450 _ => fail ~"needed dict value in source"
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)) => {
460 sources.insert(copy *k, parse_source(*k, v));
461 debug!("source: %s", *k);
464 Ok(_) => fail ~"malformed sources.json",
465 Err(e) => fail fmt!("%s:%s", filename.to_str(), e.to_str())
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" +
482 warn(~"malformed source json: " + src.name + ~" (missing name)");
487 let uuid = match p.find(&~"uuid") {
488 Some(json::String(n)) => {
490 warn(~"malformed source json: "
491 + src.name + ~", '" + n + ~"'"+
492 ~" is an invalid uuid");
498 warn(~"malformed source json: " + src.name + ~" (missing uuid)");
503 let url = match p.find(&~"url") {
504 Some(json::String(n)) => n,
506 warn(~"malformed source json: " + src.name + ~" (missing url)");
511 let method = match p.find(&~"method") {
512 Some(json::String(n)) => n,
514 warn(~"malformed source json: "
515 + src.name + ~" (missing method)");
520 let reference = match p.find(&~"ref") {
521 Some(json::String(n)) => Some(n),
526 match p.find(&~"tags") {
527 Some(json::List(js)) => {
530 json::String(ref j) => tags.grow(1u, j),
538 let description = match p.find(&~"description") {
539 Some(json::String(n)) => n,
541 warn(~"malformed source json: " + src.name
542 + ~" (missing description)");
547 let newpkg = Package {
552 description: description,
553 reference: reference,
558 match src.packages.position(|pkg| pkg.uuid == uuid) {
560 src.packages.set_elt(idx, newpkg);
561 log(debug, ~" updated package: " + src.name + ~"/" + name);
564 src.packages.push(newpkg);
568 log(debug, ~" loaded package: " + src.name + ~"/" + name);
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);
584 warn(~"malformed source.json: " + src.name +
585 ~"(source info is not a dict)");
588 warn(fmt!("%s:%s", src.name, e.to_str()));
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)) => {
603 load_one_source_package(src, p);
606 warn(~"malformed source json: " + src.name +
613 warn(~"malformed packages.json: " + src.name +
614 ~"(packages is not a list)");
617 warn(fmt!("%s:%s", src.name, e.to_str()));
622 fn build_cargo_options(argv: ~[~str]) -> Options {
623 let matches = match getopts::getopts(argv, opts()) {
626 fail fmt!("%s", getopts::fail_str(f));
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);
636 let is_install = len > 1u && matches.free[1] == ~"install";
637 let is_uninstall = len > 1u && matches.free[1] == ~"uninstall";
639 if G && g { fail ~"-G and -g both provided"; }
641 if !is_install && !is_uninstall && (g || G) {
642 fail ~"-g and -G are only valid for `install` and `uninstall|rm`";
646 if (!is_install && !is_uninstall) || g { UserMode }
647 else if G { SystemMode }
650 Options {test: test, mode: mode, free: matches.free, help: help}
653 fn configure(opts: Options) -> Cargo {
654 let home = match get_cargo_root() {
656 Err(_err) => get_cargo_sysroot().get()
659 let get_cargo_dir = match opts.mode {
660 SystemMode => get_cargo_sysroot,
661 UserMode => get_cargo_root,
662 LocalMode => get_cargo_root_nearest
665 let p = get_cargo_dir().get();
667 let sources = HashMap();
668 try_parse_sources(&home.push("sources.json"), sources);
669 try_parse_sources(&home.push("local-sources.json"), sources);
671 let dep_cache = HashMap();
674 pgp: pgp::supported(),
677 bindir: p.push("bin"),
678 libdir: p.push("lib"),
679 workdir: p.push("work"),
680 sourcedir: home.push("sources"),
682 mut current_install: ~"",
683 dep_cache: dep_cache,
688 need_dir(&c.installdir);
689 need_dir(&c.sourcedir);
690 need_dir(&c.workdir);
694 for sources.each_key |k| {
695 let mut s = sources.get(k);
696 load_source_packages(&c, s);
697 sources.insert(k, s);
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");
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| {
719 // Runs all programs in directory <buildpath>
720 fn run_programs(buildpath: &Path) {
721 let newv = os::list_dir_path(buildpath);
723 run::run_program(ct.to_str(), ~[]);
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(),
737 cf.to_str()] + extra_flags);
739 error(fmt!("rustc failed: %d\n%s\n%s", p.status, p.err, p.out));
745 fn test_one_crate(_c: &Cargo, path: &Path, cf: &Path) {
746 let buildpath = match run_in_buildpath(~"testing", path,
753 run_programs(&buildpath);
756 fn install_one_crate(c: &Cargo, path: &Path, cf: &Path) {
757 let buildpath = match run_in_buildpath(~"installing", path,
763 let newv = os::list_dir_path(&buildpath);
764 let exec_suffix = os::exe_suffix();
766 if (exec_suffix != ~"" && str::ends_with(ct.to_str(),
768 (exec_suffix == ~"" &&
769 !str::starts_with(ct.filename().get(),
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`
779 debug!(" lib: %s", ct.to_str());
780 install_to_dir(*ct, &c.libdir);
786 fn rustc_sysroot() -> ~str {
787 match os::self_exe_path() {
789 let rustc = path.push_many([~"..", ~"bin", ~"rustc"]);
790 debug!(" rustc: %s", rustc.to_str());
797 fn install_source(c: &Cargo, path: &Path) {
798 debug!("source: %s", path.to_str());
799 os::change_dir(path);
801 let mut cratefiles = ~[];
802 for os::walk_dir(&Path(".")) |p| {
803 if p.filetype() == Some(~".rc") {
808 if vec::is_empty(cratefiles) {
809 fail ~"this doesn't look like a rust package (no .rc files)";
812 for cratefiles.each |cf| {
813 match load_crate(cf) {
816 for crate.deps.each |query| {
817 // FIXME (#1356): handle cyclic dependencies
818 // (n.b. #1356 says "Cyclic dependency is an error
821 let wd = get_temp_workdir(c);
822 install_query(c, &wd, *query);
825 os::change_dir(path);
828 test_one_crate(c, path, cf);
830 install_one_crate(c, path, cf);
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();
841 run::run_program(~"git", ~[~"checkout", r]);
844 install_source(c, wd);
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]);
852 fail fmt!("fetch of %s failed: %s", url, p.err);
854 run::run_program(~"tar", ~[~"-x", ~"--strip-components=1",
856 ~"-f", tarpath.to_str()]);
857 install_source(c, wd);
860 fn install_file(c: &Cargo, wd: &Path, path: &Path) {
861 run::program_output(~"tar", ~[~"-x", ~"--strip-components=1",
863 ~"-f", path.to_str()]);
864 install_source(c, wd);
867 fn install_package(c: &Cargo, src: ~str, wd: &Path, pkg: Package) {
868 let url = copy pkg.url;
869 let method = match pkg.method {
875 info(fmt!("installing %s/%s via %s...", src, pkg.name, 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),
885 fn cargo_suggestion(c: &Cargo, fallback: fn())
887 if c.sources.size() == 0u {
888 error(~"no sources defined - you may wish to run " +
895 fn install_uuid(c: &Cargo, wd: &Path, uuid: ~str) {
897 for_each_package(c, |s, p| {
899 vec::push(&mut ps, (s.name, copy *p));
902 if vec::len(ps) == 1u {
903 let (sname, p) = copy ps[0];
904 install_package(c, sname, wd, p);
906 } else if vec::len(ps) == 0u {
907 cargo_suggestion(c, || {
908 error(~"can't find package: " + uuid);
912 error(~"found multiple packages:");
914 let (sname,p) = copy *elt;
915 info(~" " + sname + ~"/" + p.uuid + ~" (" + p.name + ~")");
919 fn install_named(c: &Cargo, wd: &Path, name: ~str) {
921 for_each_package(c, |s, p| {
923 vec::push(&mut ps, (s.name, copy *p));
926 if vec::len(ps) == 1u {
927 let (sname, p) = copy ps[0];
928 install_package(c, sname, wd, p);
930 } else if vec::len(ps) == 0u {
931 cargo_suggestion(c, || {
932 error(~"can't find package: " + name);
936 error(~"found multiple packages:");
938 let (sname,p) = copy *elt;
939 info(~" " + sname + ~"/" + p.uuid + ~" (" + p.name + ~")");
943 fn install_uuid_specific(c: &Cargo, wd: &Path, src: ~str, uuid: ~str) {
944 match c.sources.find(src) {
946 for s.packages.each |p| {
948 install_package(c, src, wd, *p);
955 error(~"can't find package: " + src + ~"/" + uuid);
958 fn install_named_specific(c: &Cargo, wd: &Path, src: ~str, name: ~str) {
959 match c.sources.find(src) {
961 for s.packages.each |p| {
963 install_package(c, src, wd, *p);
970 error(~"can't find package: " + src + ~"/" + name);
973 fn cmd_uninstall(c: &Cargo) {
974 if vec::len(c.opts.free) < 3u {
981 let target = c.opts.free[2u];
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
988 fn try_uninstall(p: &Path) -> bool {
989 if os::remove_file(p) {
990 info(~"uninstalled: '" + p.to_str() + ~"'");
993 error(~"could not uninstall: '" +
1000 for os::list_dir(lib).each |file| {
1001 match str::find_str(*file, ~"-" + target + ~"-") {
1002 Some(_) => if !try_uninstall(&lib.push(*file)) { return },
1006 error(~"can't find package with uuid: " + target);
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 },
1014 for os::list_dir(bin).each |file| {
1015 match str::find_str(*file, target) {
1016 Some(_) => if !try_uninstall(&lib.push(*file)) { return },
1021 error(~"can't find package with name: " + target);
1025 fn install_query(c: &Cargo, wd: &Path, target: ~str) {
1026 match c.dep_cache.find(target) {
1035 c.dep_cache.insert(target, true);
1037 if is_archive_path(target) {
1038 install_file(c, wd, &Path(target));
1040 } else if is_git_url(target) {
1041 let reference = if c.opts.free.len() >= 4u {
1042 Some(c.opts.free[3u])
1046 install_git(c, wd, target, reference);
1047 } else if !valid_pkg_name(target) && has_archive_extension(target) {
1048 install_curl(c, wd, target);
1051 let mut ps = copy target;
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));
1058 install_uuid_specific(c, wd, source, ps);
1060 install_named_specific(c, wd, source, ps);
1065 install_uuid(c, wd, ps);
1067 install_named(c, wd, ps);
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.
1076 if target == c.current_install {
1077 for c.dep_cache.each |k, _v| {
1078 c.dep_cache.remove(k);
1081 c.current_install = ~"";
1085 fn get_temp_workdir(c: &Cargo) -> Path {
1086 match tempfile::mkdtemp(&c.workdir, "cargo") {
1088 None => fail fmt!("needed temp dir: %s",
1093 fn cmd_install(c: &Cargo) unsafe {
1094 let wd = get_temp_workdir(c);
1096 if vec::len(c.opts.free) == 2u {
1097 let cwd = os::getcwd();
1098 let status = run::run_program(~"cp", ~[~"-R", cwd.to_str(),
1102 fail fmt!("could not copy directory: %s", cwd.to_str());
1105 install_source(c, &wd);
1111 let query = c.opts.free[2];
1112 c.current_install = query.to_str();
1114 install_query(c, &wd, query);
1117 fn sync(c: &Cargo) {
1118 for c.sources.each_key |k| {
1119 let mut s = c.sources.get(k);
1121 c.sources.insert(k, s);
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;
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()));
1143 if os::copy_file(&url.push("source.json"), &srcfile) {
1144 has_src_file = false;
1147 os::copy_file(&url.push("source.json.sig"), &srcsigfile);
1148 os::copy_file(&url.push("packages.json.sig"), &sigfile);
1150 match copy src.key {
1152 let p = run::program_output(~"curl",
1154 ~"-o", keyfile.to_str(), u]);
1156 error(fmt!("fetch for source %s (key %s) failed", name, u));
1159 pgp::add(&c.root, &keyfile);
1163 match (src.key, src.keyfp) {
1164 (Some(_), Some(f)) => {
1165 let r = pgp::verify(&c.root, &pkgfile, &sigfile);
1168 error(fmt!("signature verification failed for source %s with \
1174 let e = pgp::verify(&c.root, &srcfile, &srcsigfile);
1177 error(fmt!("signature verification failed for source %s \
1178 with key %s", name, f));
1186 copy_warn(&pkgfile, &destpkgfile);
1189 copy_warn(&srcfile, &destsrcfile);
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);
1198 info(fmt!("synced source: %s", name));
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");
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));
1217 warn(~"a past security check failed on source " +
1218 name + ~" and rolling back the source failed -"
1219 + ~" this source may be compromised");
1223 if !os::change_dir(dir) {
1224 msg(name, insecure);
1227 let p = run::program_output(~"git", ~[~"reset", ~"--hard",
1231 msg(name, insecure);
1236 if !os::path_exists(&dir.push(".git")) {
1237 let p = run::program_output(~"git", ~[~"clone", url, dir.to_str()]);
1240 error(fmt!("fetch for source %s (url %s) failed", name, url));
1245 if !os::change_dir(dir) {
1246 error(fmt!("fetch for source %s (url %s) failed", name, url));
1250 let p = run::program_output(~"git", ~[~"pull"]);
1253 error(fmt!("fetch for source %s (url %s) failed", name, url));
1258 let has_src_file = os::path_exists(&srcfile);
1260 match copy src.key {
1262 let p = run::program_output(~"curl",
1264 ~"-o", keyfile.to_str(), u]);
1266 error(fmt!("fetch for source %s (key %s) failed", name, u));
1267 rollback(name, dir, false);
1270 pgp::add(&c.root, &keyfile);
1274 match (src.key, src.keyfp) {
1275 (Some(_), Some(f)) => {
1276 let r = pgp::verify(&c.root, &pkgfile, &sigfile);
1279 error(fmt!("signature verification failed for source %s with \
1281 rollback(name, dir, false);
1286 let e = pgp::verify(&c.root, &srcfile, &srcsigfile);
1289 error(fmt!("signature verification failed for source %s \
1290 with key %s", name, f));
1291 rollback(name, dir, false);
1299 os::remove_file(&keyfile);
1301 info(fmt!("synced source: %s", name));
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;
1320 url += ~"/packages.json";
1323 let p = run::program_output(~"curl",
1325 ~"-o", pkgfile.to_str(), url]);
1328 error(fmt!("fetch for source %s (url %s) failed", name, url));
1332 url = src.url + ~"/source.json";
1334 run::program_output(~"curl",
1336 ~"-o", srcfile.to_str(), url]);
1339 has_src_file = true;
1343 match copy src.key {
1345 let p = run::program_output(~"curl",
1347 ~"-o", keyfile.to_str(), u]);
1349 error(fmt!("fetch for source %s (key %s) failed", name, u));
1352 pgp::add(&c.root, &keyfile);
1356 match (src.key, src.keyfp) {
1357 (Some(_), Some(f)) => {
1359 url = src.url + ~"/packages.json.sig";
1362 url = src.url + ~".sig";
1365 let mut p = run::program_output(~"curl",
1366 ~[~"-f", ~"-s", ~"-o",
1367 sigfile.to_str(), url]);
1369 error(fmt!("fetch for source %s (sig %s) failed", name, url));
1373 let r = pgp::verify(&c.root, &pkgfile, &sigfile);
1376 error(fmt!("signature verification failed for source %s with \
1381 if smart && has_src_file {
1382 url = src.url + ~"/source.json.sig";
1384 p = run::program_output(~"curl",
1385 ~[~"-f", ~"-s", ~"-o",
1386 srcsigfile.to_str(), url]);
1388 error(fmt!("fetch for source %s (sig %s) failed",
1393 let e = pgp::verify(&c.root, &srcfile, &srcsigfile);
1396 error(~"signature verification failed for " +
1397 ~"source " + name + ~" with key " + f);
1405 copy_warn(&pkgfile, &destpkgfile);
1407 if smart && has_src_file {
1408 copy_warn(&srcfile, &destsrcfile);
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);
1417 info(fmt!("synced source: %s", name));
1422 fn sync_one(c: &Cargo, src: @Source) {
1423 let name = src.name;
1424 let dir = c.sourcedir.push(name);
1426 info(fmt!("syncing source: %s...", name));
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)
1437 load_source_info(c, src);
1438 load_source_packages(c, src);
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";
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");
1451 run::program_output(~"curl", ~[~"-f", ~"-s",
1452 ~"-o", srcfile.to_str(), srcurl]);
1454 error(fmt!("fetch of sources.json failed: %s", p.out));
1459 run::program_output(~"curl", ~[~"-f", ~"-s",
1460 ~"-o", sigfile.to_str(), sigurl]);
1462 error(fmt!("fetch of sources.json.sig failed: %s", p.out));
1466 let r = pgp::verify(&c.root, &srcfile, &sigfile);
1468 error(fmt!("signature verification failed for '%s'",
1473 copy_warn(&srcfile, &destsrcfile);
1474 os::remove_file(&srcfile);
1475 os::remove_file(&sigfile);
1477 info(fmt!("initialized .cargo in %s", c.root.to_str()));
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, ~", ") + ~"]";
1486 if p.description != ~"" {
1487 print(~" >> " + p.description + ~"\n")
1491 fn print_source(s: @Source) {
1492 info(s.name + ~" (" + s.url + ~")");
1494 let pks = sort::merge_sort(s.packages.get(), sys::shape_lt);
1495 let l = vec::len(pks);
1497 print(io::with_str_writer(|writer| {
1498 let mut list = ~" >> ";
1500 for vec::eachi(pks) |i, pk| {
1501 if str::len(list) > 78u {
1502 writer.write_line(list);
1505 list += pk.name + (if l - 1u == i { ~"" } else { ~", " });
1508 writer.write_line(list);
1512 fn cmd_list(c: &Cargo) {
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));
1521 match c.sources.find(*name) {
1523 print_source(source);
1526 error(fmt!("no such source: %s", *name));
1532 for c.sources.each_value |v| {
1538 fn cmd_search(c: &Cargo) {
1539 if vec::len(c.opts.free) < 3u {
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) ) {
1556 info(fmt!("found %d packages", n));
1559 fn install_to_dir(srcfile: &Path, destdir: &Path) {
1560 let newfile = destdir.push(srcfile.filename().get());
1562 let status = run::run_program(~"cp", ~[~"-r", srcfile.to_str(),
1565 info(fmt!("installed: '%s'", newfile.to_str()));
1567 error(fmt!("could not install: '%s'", newfile.to_str()));
1571 fn dump_cache(c: &Cargo) {
1574 let out = c.root.push("cache.json");
1575 let _root = json::Object(~LinearMap());
1577 if os::path_exists(&out) {
1578 copy_warn(&out, &c.root.push("cache.json.old"));
1581 fn dump_sources(c: &Cargo) {
1582 if c.sources.size() < 1u {
1588 let out = c.root.push("sources.json");
1590 if os::path_exists(&out) {
1591 copy_warn(&out, &c.root.push("sources.json.old"));
1594 match io::buffered_file_writer(&out) {
1595 result::Ok(writer) => {
1596 let mut hash = ~LinearMap();
1598 for c.sources.each |k, v| {
1599 let mut chash = ~LinearMap();
1601 chash.insert(~"url", json::String(v.url));
1602 chash.insert(~"method", json::String(v.method));
1606 chash.insert(~"key", json::String(copy key));
1610 match copy v.keyfp {
1612 chash.insert(~"keyfp", json::String(copy keyfp));
1617 hash.insert(copy k, json::Object(move chash));
1620 json::to_writer(writer, &json::Object(move hash))
1623 error(fmt!("could not dump sources: %s", e));
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()));
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));
1644 let action = c.opts.free[2u];
1648 for c.sources.each_key |k| {
1649 c.sources.remove(k);
1652 info(~"cleared sources");
1655 if vec::len(c.opts.free) < 5u {
1660 let name = c.opts.free[3u];
1661 let url = c.opts.free[4u];
1663 if !valid_pkg_name(name) {
1664 error(fmt!("'%s' is an invalid source name", name));
1668 if c.sources.contains_key(name) {
1669 error(fmt!("source already exists: %s", name));
1671 c.sources.insert(name, @Source {
1674 mut method: assume_source_method(url),
1679 info(fmt!("added source: %s", name));
1683 if vec::len(c.opts.free) < 4u {
1688 let name = c.opts.free[3u];
1690 if !valid_pkg_name(name) {
1691 error(fmt!("'%s' is an invalid source name", name));
1695 if c.sources.contains_key(name) {
1696 c.sources.remove(name);
1697 info(fmt!("removed source: %s", name));
1699 error(fmt!("no such source: %s", name));
1703 if vec::len(c.opts.free) < 5u {
1708 let name = c.opts.free[3u];
1709 let url = c.opts.free[4u];
1711 if !valid_pkg_name(name) {
1712 error(fmt!("'%s' is an invalid source name", name));
1716 match c.sources.find(name) {
1718 let old = copy source.url;
1719 let method = assume_source_method(url);
1722 source.method = method;
1724 c.sources.insert(name, source);
1726 info(fmt!("changed source url: '%s' to '%s'", old, url));
1729 error(fmt!("no such source: %s", name));
1734 if vec::len(c.opts.free) < 5u {
1739 let name = c.opts.free[3u];
1740 let method = c.opts.free[4u];
1742 if !valid_pkg_name(name) {
1743 error(fmt!("'%s' is an invalid source name", name));
1747 match c.sources.find(name) {
1749 let old = copy source.method;
1751 source.method = match method {
1757 c.sources.insert(name, source);
1759 info(fmt!("changed source method: '%s' to '%s'", old,
1763 error(fmt!("no such source: %s", name));
1768 if vec::len(c.opts.free) < 5u {
1773 let name = c.opts.free[3u];
1774 let newn = c.opts.free[4u];
1776 if !valid_pkg_name(name) {
1777 error(fmt!("'%s' is an invalid source name", name));
1780 if !valid_pkg_name(newn) {
1781 error(fmt!("'%s' is an invalid source name", newn));
1785 match c.sources.find(name) {
1787 c.sources.remove(name);
1788 c.sources.insert(newn, source);
1789 info(fmt!("renamed source: %s to %s", name, newn));
1792 error(fmt!("no such source: %s", name));
1801 print(~"Usage: cargo <cmd> [options] [args..]
1802 e.g. cargo install <name>
1804 Where <cmd> is one of:
1805 init, install, list, search, sources,
1810 -h, --help Display this message
1811 <cmd> -h, <cmd> --help Display help for <cmd>
1815 fn cmd_usage_init() {
1818 Re-initialize cargo in ~/.cargo. Clears all sources and then adds the
1819 default sources from <www.rust-lang.org/sources.json>.");
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>
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/)
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.");
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]
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/)
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
1858 fn cmd_usage_list() {
1859 print(~"cargo list [sources..]
1861 If no arguments are provided, list all sources and their packages.
1862 If source names are provided, list those sources and their packages.
1866 fn cmd_usage_search() {
1867 print(~"cargo search <query | '*'> [tags..]
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>
1880 If no arguments are supplied, list all sources (but not their packages).
1883 add Add a source. The source method will be guessed
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.");
1892 let argv = os::args();
1893 let o = build_cargo_options(argv);
1895 if vec::len(o.free) < 2u {
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(),
1911 if o.free[1] == ~"usage" {
1916 let mut c = configure(o);
1918 let first_time = os::path_exists(&home.push("sources.json"));
1920 if !first_time && o.free[1] != ~"init" {
1923 // FIXME (#2662): shouldn't need to reconfigure
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),