// cargo.rs - Rust package manager
-import syntax::{ast, codemap};
-import syntax::parse;
+import syntax::{ast, codemap, parse, visit, attr};
+import syntax::diagnostic::span_handler;
+import codemap::span;
import rustc::metadata::filesearch::{get_cargo_root, get_cargo_root_nearest,
get_cargo_sysroot, libdir};
import syntax::diagnostic;
mut method: str,
mut description: str,
mut ref: option<str>,
- mut tags: [str]
+ mut tags: [str],
+ mut versions: [(str, str)]
+};
+
+type local_package = {
+ mut name: str,
+ mut metaname: str,
+ mut version: str,
+ mut files: [str]
};
type source = {
workdir: str,
sourcedir: str,
sources: map::hashmap<str, source>,
+ mut current_install: str,
+ dep_cache: map::hashmap<str, bool>,
opts: options
};
-type pkg = {
+type crate = {
mut name: str,
mut vers: str,
mut uuid: str,
mut desc: option<str>,
mut sigs: option<str>,
- mut crate_type: option<str>
+ mut crate_type: option<str>,
+ mut deps: [str]
};
type options = {
fn has_archive_extension(p: str) -> bool {
str::ends_with(p, ".tar") ||
str::ends_with(p, ".tar.gz") ||
+ str::ends_with(p, ".tar.bz2") ||
+ str::ends_with(p, ".tar.Z") ||
+ str::ends_with(p, ".tar.lz") ||
str::ends_with(p, ".tar.xz") ||
- str::ends_with(p, ".tar.bz2")
+ str::ends_with(p, ".tgz") ||
+ str::ends_with(p, ".tbz") ||
+ str::ends_with(p, ".tbz2") ||
+ str::ends_with(p, ".tb2") ||
+ str::ends_with(p, ".taz") ||
+ str::ends_with(p, ".tlz") ||
+ str::ends_with(p, ".txz")
}
fn is_archive_path(u: str) -> bool {
}
fn assume_source_method(url: str) -> str {
- if is_git_url(url) { ret "git"; }
+ if is_git_url(url) {
+ ret "git";
+ }
if str::starts_with(url, "file://") || os::path_exists(url) {
ret "file";
}
(name, vers, uuid)
}
-fn load_pkg(filename: str) -> option<pkg> {
+fn load_crate(filename: str) -> option<crate> {
let cm = codemap::new_codemap();
let handler = diagnostic::mk_handler(none);
let sess = @{
uuid = u;
}
}
- _ { fail "load_pkg: pkg attributes may not contain meta_words"; }
+ _ {
+ fail "crate attributes may not contain " +
+ "meta_words";
+ }
+ }
+ }
+
+ type env = @{
+ mut deps: [str]
+ };
+
+ fn goto_view_item(e: env, i: @ast::view_item) {
+ alt i.node {
+ ast::view_item_use(ident, metas, id) {
+ let name_items = attr::find_meta_items_by_name(metas, "name");
+ let m = if name_items.is_empty() {
+ metas + [attr::mk_name_value_item_str("name", ident)]
+ } else {
+ metas
+ };
+ let mut attr_name = ident;
+ let mut attr_vers = "";
+ let mut attr_from = "";
+
+ for m.each { |item|
+ alt attr::get_meta_item_value_str(item) {
+ some(value) {
+ let name = attr::get_meta_item_name(item);
+
+ alt name {
+ "vers" { attr_vers = value; }
+ "from" { attr_from = value; }
+ _ {}
+ }
+ }
+ none {}
+ }
+ }
+
+ let query = if !str::is_empty(attr_from) {
+ attr_from
+ } else {
+ if !str::is_empty(attr_vers) {
+ attr_name + "@" + attr_vers
+ } else { attr_name }
+ };
+
+ alt attr_name {
+ "std" | "core" { }
+ _ { e.deps += [query]; }
+ }
+ }
+ _ { }
}
}
+ fn goto_item(_e: env, _i: @ast::item) {
+ }
+
+ let e = @{
+ mut deps: []
+ };
+ let v = visit::mk_simple_visitor(@{
+ visit_view_item: bind goto_view_item(e, _),
+ visit_item: bind goto_item(e, _),
+ with *visit::default_simple_visitor()
+ });
+
+ visit::visit_crate(*c, (), v);
+
+ let deps = copy e.deps;
alt (name, vers, uuid) {
(some(name0), some(vers0), some(uuid0)) {
mut uuid: uuid0,
mut desc: desc,
mut sigs: sigs,
- mut crate_type: crate_type})
+ mut crate_type: crate_type,
+ mut deps: deps })
}
_ { ret none; }
}
mut method: method,
mut description: description,
mut ref: ref,
- mut tags: tags
+ mut tags: tags,
+ mut versions: []
};
for src.packages.each { |pkg|
pkg.description = newpkg.description;
pkg.ref = newpkg.ref;
pkg.tags = newpkg.tags;
+ pkg.versions = newpkg.versions;
log(debug, " updated package: " + src.name + "/" + name);
ret;
}
try_parse_sources(path::connect(home, "sources.json"), sources);
try_parse_sources(path::connect(home, "local-sources.json"), sources);
+ let dep_cache = map::str_hash::<bool>();
+
let mut c = {
pgp: pgp::supported(),
root: home,
workdir: path::connect(home, "work"),
sourcedir: path::connect(home, "sources"),
sources: sources,
+ mut current_install: "",
+ dep_cache: dep_cache,
opts: opts
};
}
for cratefiles.each {|cf|
- let p = load_pkg(cf);
- alt p {
+ alt load_crate(cf) {
none { cont; }
- some(_) {
+ some(crate) {
+ for crate.deps.each { |query|
+ // TODO: handle cyclic dependencies
+
+ let wd_base = c.workdir + path::path_sep();
+ let wd = alt tempfile::mkdtemp(wd_base, "") {
+ some(_wd) { _wd }
+ none { fail #fmt("needed temp dir: %s", wd_base); }
+ };
+
+ install_query(c, wd, query);
+ }
+
+ os::change_dir(path);
+
if c.opts.test {
test_one_crate(c, path, cf);
}
}
}
-fn cmd_install(&c: cargo) unsafe {
- let wd_base = c.workdir + path::path_sep();
- let wd = alt tempfile::mkdtemp(wd_base, "") {
- some(_wd) { _wd }
- none { fail #fmt("needed temp dir: %s", wd_base); }
- };
-
- if vec::len(c.opts.free) == 2u {
- let cwd = os::getcwd();
- let status = run::run_program("cp", ["-R", cwd, wd]);
-
- if status != 0 {
- fail #fmt("could not copy directory: %s", cwd);
+fn install_query(&c: cargo, wd: str, target: str) {
+ alt c.dep_cache.find(target) {
+ some(_inst) {
+ if _inst {
+ ret;
+ }
}
-
- install_source(c, wd);
- ret;
+ none {}
}
- sync(c);
-
- let target = c.opts.free[2];
+ c.dep_cache.insert(target, true);
if is_archive_path(target) {
install_file(c, wd, target);
} else {
none
};
- install_git(c, wd, target, ref)
+ install_git(c, wd, target, ref);
} else if !valid_pkg_name(target) && has_archive_extension(target) {
install_curl(c, wd, target);
ret;
}
}
}
+
+ // FIXME: This whole dep_cache and current_install
+ // thing is a bit of a hack. It should be cleaned up in the future.
+
+ if target == c.current_install {
+ for c.dep_cache.each { |k, _v|
+ c.dep_cache.remove(k);
+ }
+
+ c.current_install = "";
+ }
+}
+
+fn cmd_install(&c: cargo) unsafe {
+ let wd_base = c.workdir + path::path_sep();
+ let wd = alt tempfile::mkdtemp(wd_base, "") {
+ some(_wd) { _wd }
+ none { fail #fmt("needed temp dir: %s", wd_base); }
+ };
+
+ if vec::len(c.opts.free) == 2u {
+ let cwd = os::getcwd();
+ let status = run::run_program("cp", ["-R", cwd, wd]);
+
+ if status != 0 {
+ fail #fmt("could not copy directory: %s", cwd);
+ }
+
+ install_source(c, wd);
+ ret;
+ }
+
+ sync(c);
+
+ let query = c.opts.free[2];
+ c.current_install = copy query;
+
+ install_query(c, wd, copy query);
}
fn sync(&c: cargo) {
for c.sources.each_key { |k|
let mut s = c.sources.get(k);
-
sync_one(c, s);
- // FIXME: mutability hack
c.sources.insert(k, s);
}
}
let p = run::program_output("curl", ["-f", "-s", "-o", srcfile, srcurl]);
if p.status != 0 {
- warn(#fmt["fetch of sources.json failed: %s", p.out]);
+ error(#fmt["fetch of sources.json failed: %s", p.out]);
ret;
}
let p = run::program_output("curl", ["-f", "-s", "-o", sigfile, sigurl]);
if p.status != 0 {
- warn(#fmt["fetch of sources.json.sig failed: %s", p.out]);
+ error(#fmt["fetch of sources.json.sig failed: %s", p.out]);
ret;
}
}
}
-fn dump_cache(c: cargo) {
+fn dump_cache(&c: cargo) {
need_dir(c.root);
let out = path::connect(c.root, "cache.json");
- let root = json::dict(map::str_hash());
+ let _root = json::dict(map::str_hash());
if os::path_exists(out) {
copy_warn(out, path::connect(c.root, "cache.json.old"));
}
}
-fn dump_sources(c: cargo) {
+fn dump_sources(&c: cargo) {
+ if c.sources.size() < 1u {
+ ret;
+ }
+
need_dir(c.root);
let out = path::connect(c.root, "sources.json");
alt c.sources.find(name) {
some(source) {
let old = copy source.url;
+ let method = assume_source_method(url);
- source.url = if source.method == "file" {
- os::make_absolute(url)
- } else {
- url
- };
+ source.url = url;
+ source.method = method;
+
+ c.sources.insert(name, source);
info(#fmt("changed source url: '%s' to '%s'", old, url));
}
_ { "curl" }
};
+ c.sources.insert(name, source);
+
info(#fmt("changed source method: '%s' to '%s'", old,
method));
}
}
fn cmd_usage() {
- print("Usage: cargo <verb> [options] [args..]\n" +
- " e.g.: cargo [init | sync]\n" +
- " e.g.: cargo install [-g | -G] <package>
-
-General:
- init Reinitialize cargo in ~/.cargo
- usage Display this message
-
-Querying:
- list [sources..] List the packages in sources
- search <name | '*'> [tags...] Search packages
-
-Sources:
- sources List sources
- sources add <name> <url> Add a source
- sources remove <name> Remove a source
- sources rename <name> <new> Rename a source
- sources set-url <name> <url> Change the source URL
- sources set-method <name> <method> Change the method (guesses from
- the URL by default) can be ;git',
- 'file' or 'curl'
- sources clear Remove all sources
-
-Packages:
- install [options] Install a package from source
- code in the current directory
- install [options] [source/]<name> Install a package by name
- install [options] [source/]<uuid> Install a package by uuid
- install [options] <url> Install a package via curl (HTTP,
- FTP, etc.) from an
- .tar[.gz|bz2|xz] file
- install [options] <url> [ref] Install a package via read-only
- git
- install [options] <file> Install a package directly from an
- .tar[.gz|bz2|xz] file
- uninstall [options] [source/]<name> Remove a package by [meta]name
- uninstall [options] [source/]<uuid> Remove a package by [meta]uuid
-
-Package installation options:
- --test Run crate tests before installing
-
-Package [un]installation options:
- -g Work at the user level (~/.cargo/bin/ instead of
- locally in ./.cargo/bin/ by default)
- -G Work at the system level (/usr/local/lib/cargo/bin/)
-
-Other:
- -h, --help Display this message
+ print("Usage: cargo <cmd> [options] [args..]
+e.g. cargo install <name>
+
+Where <cmd> is one of:
+ init, install, list, search, sources,
+ uninstall, usage
+
+Options:
+
+ -h, --help Display this message
+ <cmd> -h, <cmd> --help Display help for <cmd>
");
}
+fn cmd_usage_init() {
+ print("cargo init
+
+Re-initialize cargo in ~/.cargo. Clears all sources and then adds the
+default sources from <www.rust-lang.org/sources.json>.");
+}
+
+fn cmd_usage_install() {
+ print("cargo install
+cargo install [source/]<name>[@version]
+cargo install [source/]<uuid>[@version]
+cargo install <git url> [ref]
+cargo install <tarball url>
+cargo install <tarball file>
+
+Options:
+ --test Run crate tests before installing
+ -g Install to the user level (~/.cargo/bin/ instead of
+ locally in ./.cargo/bin/ by default)
+ -G Install to the system level (/usr/local/lib/cargo/bin/)
+
+Install a crate. If no arguments are supplied, it installs from
+the current working directory. If a source is provided, only install
+from that source, otherwise it installs from any source.");
+}
+
+fn cmd_usage_uninstall() {
+ print("cargo uninstall [source/]<name>[@version]
+cargo uninstall [source/]<uuid>[@version]
+cargo uninstall <meta-name>[@version]
+cargo uninstall <meta-uuid>[@version]
+
+Options:
+ -g Remove from the user level (~/.cargo/bin/ instead of
+ locally in ./.cargo/bin/ by default)
+ -G Remove from the system level (/usr/local/lib/cargo/bin/)
+
+Remove a crate. If a source is provided, only remove
+from that source, otherwise it removes from any source.
+If a crate was installed directly (git, tarball, etc.), you can remove
+it by metadata.");
+}
+
+fn cmd_usage_list() {
+ print("cargo list [sources..]
+
+If no arguments are provided, list all sources and their packages.
+If source names are provided, list those sources and their packages.
+");
+}
+
+fn cmd_usage_search() {
+ print("cargo search <query | '*'> [tags..]
+
+Search packages.");
+}
+
+fn cmd_usage_sources() {
+ print("cargo sources
+cargo sources add <name> <url>
+cargo sources remove <name>
+cargo sources rename <name> <new>
+cargo sources set-url <name> <url>
+cargo sources set-method <name> <method>
+
+If no arguments are supplied, list all sources (but not their packages).
+
+Commands:
+ add Add a source. The source method will be guessed
+ from the URL.
+ remove Remove a source.
+ rename Rename a source.
+ set-url Change the URL for a source.
+ set-method Change the method for a source.");
+}
+
fn main(argv: [str]) {
let o = build_cargo_options(argv);
- if vec::len(o.free) < 2u || o.help {
+ if vec::len(o.free) < 2u {
+ cmd_usage();
+ ret;
+ }
+ if o.help {
+ alt o.free[1] {
+ "init" { cmd_usage_init(); }
+ "install" { cmd_usage_install(); }
+ "uninstall" { cmd_usage_uninstall(); }
+ "list" { cmd_usage_list(); }
+ "search" { cmd_usage_search(); }
+ "sources" { cmd_usage_sources(); }
+ _ { cmd_usage(); }
+ }
+ ret;
+ }
+ if o.free[1] == "usage" {
cmd_usage();
ret;
}