1947 lines
54 KiB
Rust
1947 lines
54 KiB
Rust
// cargo.rs - Rust package manager
|
|
|
|
use syntax::{ast, codemap, parse, visit, attr};
|
|
use syntax::diagnostic::span_handler;
|
|
use codemap::span;
|
|
use rustc::metadata::filesearch::{get_cargo_root, get_cargo_root_nearest,
|
|
get_cargo_sysroot, libdir};
|
|
use syntax::diagnostic;
|
|
|
|
use result::{Ok, Err};
|
|
use io::WriterUtil;
|
|
use std::{map, json, tempfile, term, sort, getopts};
|
|
use map::HashMap;
|
|
use to_str::to_str;
|
|
use getopts::{optflag, optopt, opt_present};
|
|
use dvec::DVec;
|
|
|
|
type package = {
|
|
name: ~str,
|
|
uuid: ~str,
|
|
url: ~str,
|
|
method: ~str,
|
|
description: ~str,
|
|
reference: Option<~str>,
|
|
tags: ~[~str],
|
|
versions: ~[(~str, ~str)]
|
|
};
|
|
|
|
impl package : cmp::Ord {
|
|
pure fn lt(&&other: package) -> bool {
|
|
if self.name.lt(other.name) { return true; }
|
|
if other.name.lt(self.name) { return false; }
|
|
if self.uuid.lt(other.uuid) { return true; }
|
|
if other.uuid.lt(self.uuid) { return false; }
|
|
if self.url.lt(other.url) { return true; }
|
|
if other.url.lt(self.url) { return false; }
|
|
if self.method.lt(other.method) { return true; }
|
|
if other.method.lt(self.method) { return false; }
|
|
if self.description.lt(other.description) { return true; }
|
|
if other.description.lt(self.description) { return false; }
|
|
if self.tags.lt(other.tags) { return true; }
|
|
if other.tags.lt(self.tags) { return false; }
|
|
if self.versions.lt(other.versions) { return true; }
|
|
return false;
|
|
}
|
|
pure fn le(&&other: package) -> bool { !other.lt(self) }
|
|
pure fn ge(&&other: package) -> bool { !self.lt(other) }
|
|
pure fn gt(&&other: package) -> bool { other.lt(self) }
|
|
}
|
|
|
|
type local_package = {
|
|
name: ~str,
|
|
metaname: ~str,
|
|
version: ~str,
|
|
files: ~[~str]
|
|
};
|
|
|
|
type source = @{
|
|
name: ~str,
|
|
mut url: ~str,
|
|
mut method: ~str,
|
|
mut key: Option<~str>,
|
|
mut keyfp: Option<~str>,
|
|
packages: DVec<package>
|
|
};
|
|
|
|
type cargo = {
|
|
pgp: bool,
|
|
root: Path,
|
|
installdir: Path,
|
|
bindir: Path,
|
|
libdir: Path,
|
|
workdir: Path,
|
|
sourcedir: Path,
|
|
sources: map::HashMap<~str, source>,
|
|
mut current_install: ~str,
|
|
dep_cache: map::HashMap<~str, bool>,
|
|
opts: options
|
|
};
|
|
|
|
type crate = {
|
|
name: ~str,
|
|
vers: ~str,
|
|
uuid: ~str,
|
|
desc: Option<~str>,
|
|
sigs: Option<~str>,
|
|
crate_type: Option<~str>,
|
|
deps: ~[~str]
|
|
};
|
|
|
|
type options = {
|
|
test: bool,
|
|
mode: mode,
|
|
free: ~[~str],
|
|
help: bool,
|
|
};
|
|
|
|
enum mode { system_mode, user_mode, local_mode }
|
|
|
|
impl mode : cmp::Eq {
|
|
pure fn eq(&&other: mode) -> bool {
|
|
(self as uint) == (other as uint)
|
|
}
|
|
pure fn ne(&&other: mode) -> bool { !self.eq(other) }
|
|
}
|
|
|
|
fn opts() -> ~[getopts::Opt] {
|
|
~[optflag(~"g"), optflag(~"G"), optflag(~"test"),
|
|
optflag(~"h"), optflag(~"help")]
|
|
}
|
|
|
|
fn info(msg: ~str) {
|
|
let out = io::stdout();
|
|
|
|
if term::color_supported() {
|
|
term::fg(out, term::color_green);
|
|
out.write_str(~"info: ");
|
|
term::reset(out);
|
|
out.write_line(msg);
|
|
} else { out.write_line(~"info: " + msg); }
|
|
}
|
|
|
|
fn warn(msg: ~str) {
|
|
let out = io::stdout();
|
|
|
|
if term::color_supported() {
|
|
term::fg(out, term::color_yellow);
|
|
out.write_str(~"warning: ");
|
|
term::reset(out);
|
|
out.write_line(msg);
|
|
}else { out.write_line(~"warning: " + msg); }
|
|
}
|
|
|
|
fn error(msg: ~str) {
|
|
let out = io::stdout();
|
|
|
|
if term::color_supported() {
|
|
term::fg(out, term::color_red);
|
|
out.write_str(~"error: ");
|
|
term::reset(out);
|
|
out.write_line(msg);
|
|
}
|
|
else { out.write_line(~"error: " + msg); }
|
|
}
|
|
|
|
fn is_uuid(id: ~str) -> bool {
|
|
let parts = str::split_str(id, ~"-");
|
|
if vec::len(parts) == 5u {
|
|
let mut correct = 0u;
|
|
for vec::eachi(parts) |i, part| {
|
|
fn is_hex_digit(ch: char) -> bool {
|
|
('0' <= ch && ch <= '9') ||
|
|
('a' <= ch && ch <= 'f') ||
|
|
('A' <= ch && ch <= 'F')
|
|
}
|
|
|
|
if !part.all(is_hex_digit) {
|
|
return false;
|
|
}
|
|
|
|
match i {
|
|
0u => {
|
|
if str::len(part) == 8u {
|
|
correct += 1u;
|
|
}
|
|
}
|
|
1u | 2u | 3u => {
|
|
if str::len(part) == 4u {
|
|
correct += 1u;
|
|
}
|
|
}
|
|
4u => {
|
|
if str::len(part) == 12u {
|
|
correct += 1u;
|
|
}
|
|
}
|
|
_ => { }
|
|
}
|
|
}
|
|
if correct >= 5u {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_uuid() {
|
|
assert is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaafAF09");
|
|
assert !is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaaa-aaaaaaaaaaaa");
|
|
assert !is_uuid(~"");
|
|
assert !is_uuid(~"aaaaaaaa-aaa -aaaa-aaaa-aaaaaaaaaaaa");
|
|
assert !is_uuid(~"aaaaaaaa-aaa!-aaaa-aaaa-aaaaaaaaaaaa");
|
|
assert !is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-a");
|
|
assert !is_uuid(~"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaป");
|
|
}
|
|
|
|
// FIXME (#2661): implement url/URL parsing so we don't have to resort
|
|
// to weak checks
|
|
|
|
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, ~".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 {
|
|
has_archive_extension(u) && os::path_exists(&Path(u))
|
|
}
|
|
|
|
fn is_archive_url(u: ~str) -> bool {
|
|
// FIXME (#2661): this requires the protocol bit - if we had proper
|
|
// url parsing, we wouldn't need it
|
|
|
|
match str::find_str(u, ~"://") {
|
|
option::Some(_) => has_archive_extension(u),
|
|
_ => false
|
|
}
|
|
}
|
|
|
|
fn is_git_url(url: ~str) -> bool {
|
|
if str::ends_with(url, ~"/") { str::ends_with(url, ~".git/") }
|
|
else {
|
|
str::starts_with(url, ~"git://") || str::ends_with(url, ~".git")
|
|
}
|
|
}
|
|
|
|
fn assume_source_method(url: ~str) -> ~str {
|
|
if is_git_url(url) {
|
|
return ~"git";
|
|
}
|
|
if str::starts_with(url, ~"file://") || os::path_exists(&Path(url)) {
|
|
return ~"file";
|
|
}
|
|
|
|
~"curl"
|
|
}
|
|
|
|
fn load_link(mis: ~[@ast::meta_item]) -> (Option<~str>,
|
|
Option<~str>,
|
|
Option<~str>) {
|
|
let mut name = None;
|
|
let mut vers = None;
|
|
let mut uuid = None;
|
|
for mis.each |a| {
|
|
match a.node {
|
|
ast::meta_name_value(v, {node: ast::lit_str(s), span: _}) => {
|
|
match v {
|
|
~"name" => name = Some(*s),
|
|
~"vers" => vers = Some(*s),
|
|
~"uuid" => uuid = Some(*s),
|
|
_ => { }
|
|
}
|
|
}
|
|
_ => fail ~"load_link: meta items must be name-values"
|
|
}
|
|
}
|
|
(name, vers, uuid)
|
|
}
|
|
|
|
fn load_crate(filename: &Path) -> Option<crate> {
|
|
let sess = parse::new_parse_sess(None);
|
|
let c = parse::parse_crate_from_crate_file(filename, ~[], sess);
|
|
|
|
let mut name = None;
|
|
let mut vers = None;
|
|
let mut uuid = None;
|
|
let mut desc = None;
|
|
let mut sigs = None;
|
|
let mut crate_type = None;
|
|
|
|
for c.node.attrs.each |a| {
|
|
match a.node.value.node {
|
|
ast::meta_name_value(v, {node: ast::lit_str(_), span: _}) => {
|
|
match v {
|
|
~"desc" => desc = Some(v),
|
|
~"sigs" => sigs = Some(v),
|
|
~"crate_type" => crate_type = Some(v),
|
|
_ => { }
|
|
}
|
|
}
|
|
ast::meta_list(v, mis) => {
|
|
if v == ~"link" {
|
|
let (n, v, u) = load_link(mis);
|
|
name = n;
|
|
vers = v;
|
|
uuid = u;
|
|
}
|
|
}
|
|
_ => {
|
|
fail ~"crate attributes may not contain " +
|
|
~"meta_words";
|
|
}
|
|
}
|
|
}
|
|
|
|
type env = @{
|
|
mut deps: ~[~str]
|
|
};
|
|
|
|
fn goto_view_item(ps: syntax::parse::parse_sess, e: env,
|
|
i: @ast::view_item) {
|
|
match i.node {
|
|
ast::view_item_use(ident, metas, _) => {
|
|
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", *ps.interner.get(ident))]
|
|
} else {
|
|
metas
|
|
};
|
|
let mut attr_name = ident;
|
|
let mut attr_vers = ~"";
|
|
let mut attr_from = ~"";
|
|
|
|
for m.each |item| {
|
|
match attr::get_meta_item_value_str(item) {
|
|
Some(value) => {
|
|
let name = attr::get_meta_item_name(item);
|
|
|
|
match 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) {
|
|
ps.interner.get(attr_name) + ~"@" + attr_vers
|
|
} else { *ps.interner.get(attr_name) }
|
|
};
|
|
|
|
match *ps.interner.get(attr_name) {
|
|
~"std" | ~"core" => (),
|
|
_ => vec::push(e.deps, query)
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
}
|
|
fn goto_item(_e: env, _i: @ast::item) {
|
|
}
|
|
|
|
let e = @{
|
|
mut deps: ~[]
|
|
};
|
|
let v = visit::mk_simple_visitor(@{
|
|
visit_view_item: |a| goto_view_item(sess, e, a),
|
|
visit_item: |a| goto_item(e, a),
|
|
.. *visit::default_simple_visitor()
|
|
});
|
|
|
|
visit::visit_crate(*c, (), v);
|
|
|
|
let deps = copy e.deps;
|
|
|
|
match (name, vers, uuid) {
|
|
(Some(name0), Some(vers0), Some(uuid0)) => {
|
|
Some({
|
|
name: name0,
|
|
vers: vers0,
|
|
uuid: uuid0,
|
|
desc: desc,
|
|
sigs: sigs,
|
|
crate_type: crate_type,
|
|
deps: deps })
|
|
}
|
|
_ => return None
|
|
}
|
|
}
|
|
|
|
fn print(s: ~str) {
|
|
io::stdout().write_line(s);
|
|
}
|
|
|
|
fn rest(s: ~str, start: uint) -> ~str {
|
|
if (start >= str::len(s)) {
|
|
~""
|
|
} else {
|
|
str::slice(s, start, str::len(s))
|
|
}
|
|
}
|
|
|
|
fn need_dir(s: &Path) {
|
|
if os::path_is_dir(s) { return; }
|
|
if !os::make_dir(s, 493_i32 /* oct: 755 */) {
|
|
fail fmt!("can't make_dir %s", s.to_str());
|
|
}
|
|
}
|
|
|
|
fn valid_pkg_name(s: ~str) -> bool {
|
|
fn is_valid_digit(c: char) -> bool {
|
|
('0' <= c && c <= '9') ||
|
|
('a' <= c && c <= 'z') ||
|
|
('A' <= c && c <= 'Z') ||
|
|
c == '-' ||
|
|
c == '_'
|
|
}
|
|
|
|
s.all(is_valid_digit)
|
|
}
|
|
|
|
fn parse_source(name: ~str, j: json::Json) -> source {
|
|
if !valid_pkg_name(name) {
|
|
fail fmt!("'%s' is an invalid source name", name);
|
|
}
|
|
|
|
match j {
|
|
json::Dict(j) => {
|
|
let mut url = match j.find(~"url") {
|
|
Some(json::String(u)) => *u,
|
|
_ => fail ~"needed 'url' field in source"
|
|
};
|
|
let method = match j.find(~"method") {
|
|
Some(json::String(u)) => *u,
|
|
_ => assume_source_method(url)
|
|
};
|
|
let key = match j.find(~"key") {
|
|
Some(json::String(u)) => Some(*u),
|
|
_ => None
|
|
};
|
|
let keyfp = match j.find(~"keyfp") {
|
|
Some(json::String(u)) => Some(*u),
|
|
_ => None
|
|
};
|
|
if method == ~"file" {
|
|
url = os::make_absolute(&Path(url)).to_str();
|
|
}
|
|
return @{
|
|
name: name,
|
|
mut url: url,
|
|
mut method: method,
|
|
mut key: key,
|
|
mut keyfp: keyfp,
|
|
packages: DVec() };
|
|
}
|
|
_ => fail ~"needed dict value in source"
|
|
};
|
|
}
|
|
|
|
fn try_parse_sources(filename: &Path, sources: map::HashMap<~str, source>) {
|
|
if !os::path_exists(filename) { return; }
|
|
let c = io::read_whole_file_str(filename);
|
|
match json::from_str(result::get(c)) {
|
|
Ok(json::Dict(j)) => {
|
|
for j.each |k, v| {
|
|
sources.insert(k, parse_source(k, v));
|
|
debug!("source: %s", k);
|
|
}
|
|
}
|
|
Ok(_) => fail ~"malformed sources.json",
|
|
Err(e) => fail fmt!("%s:%s", filename.to_str(), e.to_str())
|
|
}
|
|
}
|
|
|
|
fn load_one_source_package(src: source, p: map::HashMap<~str, json::Json>) {
|
|
let name = match p.find(~"name") {
|
|
Some(json::String(n)) => {
|
|
if !valid_pkg_name(*n) {
|
|
warn(~"malformed source json: "
|
|
+ src.name + ~", '" + *n + ~"'"+
|
|
~" is an invalid name (alphanumeric, underscores and" +
|
|
~" dashes only)");
|
|
return;
|
|
}
|
|
*n
|
|
}
|
|
_ => {
|
|
warn(~"malformed source json: " + src.name + ~" (missing name)");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let uuid = match p.find(~"uuid") {
|
|
Some(json::String(n)) => {
|
|
if !is_uuid(*n) {
|
|
warn(~"malformed source json: "
|
|
+ src.name + ~", '" + *n + ~"'"+
|
|
~" is an invalid uuid");
|
|
return;
|
|
}
|
|
*n
|
|
}
|
|
_ => {
|
|
warn(~"malformed source json: " + src.name + ~" (missing uuid)");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let url = match p.find(~"url") {
|
|
Some(json::String(n)) => *n,
|
|
_ => {
|
|
warn(~"malformed source json: " + src.name + ~" (missing url)");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let method = match p.find(~"method") {
|
|
Some(json::String(n)) => *n,
|
|
_ => {
|
|
warn(~"malformed source json: "
|
|
+ src.name + ~" (missing method)");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let reference = match p.find(~"ref") {
|
|
Some(json::String(n)) => Some(*n),
|
|
_ => None
|
|
};
|
|
|
|
let mut tags = ~[];
|
|
match p.find(~"tags") {
|
|
Some(json::List(js)) => {
|
|
for (*js).each |j| {
|
|
match j {
|
|
json::String(j) => vec::grow(tags, 1u, *j),
|
|
_ => ()
|
|
}
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
|
|
let description = match p.find(~"description") {
|
|
Some(json::String(n)) => *n,
|
|
_ => {
|
|
warn(~"malformed source json: " + src.name
|
|
+ ~" (missing description)");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let newpkg = {
|
|
name: name,
|
|
uuid: uuid,
|
|
url: url,
|
|
method: method,
|
|
description: description,
|
|
reference: reference,
|
|
tags: tags,
|
|
versions: ~[]
|
|
};
|
|
|
|
match src.packages.position(|pkg| pkg.uuid == uuid) {
|
|
Some(idx) => {
|
|
src.packages.set_elt(idx, newpkg);
|
|
log(debug, ~" updated package: " + src.name + ~"/" + name);
|
|
}
|
|
None => {
|
|
src.packages.push(newpkg);
|
|
}
|
|
}
|
|
|
|
log(debug, ~" loaded package: " + src.name + ~"/" + name);
|
|
}
|
|
|
|
fn load_source_info(c: &cargo, src: source) {
|
|
let dir = c.sourcedir.push(src.name);
|
|
let srcfile = dir.push("source.json");
|
|
if !os::path_exists(&srcfile) { return; }
|
|
let srcstr = io::read_whole_file_str(&srcfile);
|
|
match json::from_str(result::get(srcstr)) {
|
|
Ok(json::Dict(s)) => {
|
|
let o = parse_source(src.name, json::Dict(s));
|
|
|
|
src.key = o.key;
|
|
src.keyfp = o.keyfp;
|
|
}
|
|
Ok(_) => {
|
|
warn(~"malformed source.json: " + src.name +
|
|
~"(source info is not a dict)");
|
|
}
|
|
Err(e) => {
|
|
warn(fmt!("%s:%s", src.name, e.to_str()));
|
|
}
|
|
};
|
|
}
|
|
fn load_source_packages(c: &cargo, src: source) {
|
|
log(debug, ~"loading source: " + src.name);
|
|
let dir = c.sourcedir.push(src.name);
|
|
let pkgfile = dir.push("packages.json");
|
|
if !os::path_exists(&pkgfile) { return; }
|
|
let pkgstr = io::read_whole_file_str(&pkgfile);
|
|
match json::from_str(result::get(pkgstr)) {
|
|
Ok(json::List(js)) => {
|
|
for (*js).each |j| {
|
|
match j {
|
|
json::Dict(p) => {
|
|
load_one_source_package(src, p);
|
|
}
|
|
_ => {
|
|
warn(~"malformed source json: " + src.name +
|
|
~" (non-dict pkg)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(_) => {
|
|
warn(~"malformed packages.json: " + src.name +
|
|
~"(packages is not a list)");
|
|
}
|
|
Err(e) => {
|
|
warn(fmt!("%s:%s", src.name, e.to_str()));
|
|
}
|
|
};
|
|
}
|
|
|
|
fn build_cargo_options(argv: ~[~str]) -> options {
|
|
let matches = match getopts::getopts(argv, opts()) {
|
|
result::Ok(m) => m,
|
|
result::Err(f) => {
|
|
fail fmt!("%s", getopts::fail_str(f));
|
|
}
|
|
};
|
|
|
|
let test = opt_present(matches, ~"test");
|
|
let G = opt_present(matches, ~"G");
|
|
let g = opt_present(matches, ~"g");
|
|
let help = opt_present(matches, ~"h") || opt_present(matches, ~"help");
|
|
let len = vec::len(matches.free);
|
|
|
|
let is_install = len > 1u && matches.free[1] == ~"install";
|
|
let is_uninstall = len > 1u && matches.free[1] == ~"uninstall";
|
|
|
|
if G && g { fail ~"-G and -g both provided"; }
|
|
|
|
if !is_install && !is_uninstall && (g || G) {
|
|
fail ~"-g and -G are only valid for `install` and `uninstall|rm`";
|
|
}
|
|
|
|
let mode =
|
|
if (!is_install && !is_uninstall) || g { user_mode }
|
|
else if G { system_mode }
|
|
else { local_mode };
|
|
|
|
{test: test, mode: mode, free: matches.free, help: help}
|
|
}
|
|
|
|
fn configure(opts: options) -> cargo {
|
|
let home = match get_cargo_root() {
|
|
Ok(home) => home,
|
|
Err(_err) => result::get(get_cargo_sysroot())
|
|
};
|
|
|
|
let get_cargo_dir = match opts.mode {
|
|
system_mode => get_cargo_sysroot,
|
|
user_mode => get_cargo_root,
|
|
local_mode => get_cargo_root_nearest
|
|
};
|
|
|
|
let p = result::get(get_cargo_dir());
|
|
|
|
let sources = map::str_hash();
|
|
try_parse_sources(&home.push("sources.json"), sources);
|
|
try_parse_sources(&home.push("local-sources.json"), sources);
|
|
|
|
let dep_cache = map::str_hash();
|
|
|
|
let mut c = {
|
|
pgp: pgp::supported(),
|
|
root: home,
|
|
installdir: p,
|
|
bindir: p.push("bin"),
|
|
libdir: p.push("lib"),
|
|
workdir: p.push("work"),
|
|
sourcedir: home.push("sources"),
|
|
sources: sources,
|
|
mut current_install: ~"",
|
|
dep_cache: dep_cache,
|
|
opts: opts
|
|
};
|
|
|
|
need_dir(&c.root);
|
|
need_dir(&c.installdir);
|
|
need_dir(&c.sourcedir);
|
|
need_dir(&c.workdir);
|
|
need_dir(&c.libdir);
|
|
need_dir(&c.bindir);
|
|
|
|
for sources.each_key |k| {
|
|
let mut s = sources.get(k);
|
|
load_source_packages(&c, s);
|
|
sources.insert(k, s);
|
|
}
|
|
|
|
if c.pgp {
|
|
pgp::init(&c.root);
|
|
} else {
|
|
warn(~"command `gpg` was not found");
|
|
warn(~"you have to install gpg from source " +
|
|
~" or package manager to get it to work correctly");
|
|
}
|
|
|
|
c
|
|
}
|
|
|
|
fn for_each_package(c: &cargo, b: fn(source, package)) {
|
|
for c.sources.each_value |v| {
|
|
for v.packages.each |p| {
|
|
b(v, p);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Runs all programs in directory <buildpath>
|
|
fn run_programs(buildpath: &Path) {
|
|
let newv = os::list_dir_path(buildpath);
|
|
for newv.each |ct| {
|
|
run::run_program(ct.to_str(), ~[]);
|
|
}
|
|
}
|
|
|
|
// Runs rustc in <path + subdir> with the given flags
|
|
// and returns <patho + subdir>
|
|
fn run_in_buildpath(what: &str, path: &Path, subdir: &Path, cf: &Path,
|
|
extra_flags: ~[~str]) -> Option<Path> {
|
|
let buildpath = path.push_rel(subdir);
|
|
need_dir(&buildpath);
|
|
debug!("%s: %s -> %s", what, cf.to_str(), buildpath.to_str());
|
|
let p = run::program_output(rustc_sysroot(),
|
|
~[~"--out-dir",
|
|
buildpath.to_str(),
|
|
cf.to_str()] + extra_flags);
|
|
if p.status != 0 {
|
|
error(fmt!("rustc failed: %d\n%s\n%s", p.status, p.err, p.out));
|
|
return None;
|
|
}
|
|
Some(buildpath)
|
|
}
|
|
|
|
fn test_one_crate(_c: &cargo, path: &Path, cf: &Path) {
|
|
let buildpath = match run_in_buildpath(~"testing", path,
|
|
&Path("test"),
|
|
cf,
|
|
~[ ~"--test"]) {
|
|
None => return,
|
|
Some(bp) => bp
|
|
};
|
|
run_programs(&buildpath);
|
|
}
|
|
|
|
fn install_one_crate(c: &cargo, path: &Path, cf: &Path) {
|
|
let buildpath = match run_in_buildpath(~"installing", path,
|
|
&Path("build"),
|
|
cf, ~[]) {
|
|
None => return,
|
|
Some(bp) => bp
|
|
};
|
|
let newv = os::list_dir_path(&buildpath);
|
|
let exec_suffix = os::exe_suffix();
|
|
for newv.each |ct| {
|
|
if (exec_suffix != ~"" && str::ends_with(ct.to_str(),
|
|
exec_suffix)) ||
|
|
(exec_suffix == ~"" &&
|
|
!str::starts_with(option::get(ct.filename()),
|
|
~"lib")) {
|
|
debug!(" bin: %s", ct.to_str());
|
|
install_to_dir(ct, &c.bindir);
|
|
if c.opts.mode == system_mode {
|
|
// FIXME (#2662): Put this file in PATH / symlink it so it can
|
|
// be used as a generic executable
|
|
// `cargo install -G rustray` and `rustray file.obj`
|
|
}
|
|
} else {
|
|
debug!(" lib: %s", ct.to_str());
|
|
install_to_dir(ct, &c.libdir);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fn rustc_sysroot() -> ~str {
|
|
match os::self_exe_path() {
|
|
Some(path) => {
|
|
let rustc = path.push_many([~"..", ~"bin", ~"rustc"]);
|
|
debug!(" rustc: %s", rustc.to_str());
|
|
rustc.to_str()
|
|
}
|
|
None => ~"rustc"
|
|
}
|
|
}
|
|
|
|
fn install_source(c: &cargo, path: &Path) {
|
|
debug!("source: %s", path.to_str());
|
|
os::change_dir(path);
|
|
|
|
let mut cratefiles = ~[];
|
|
for os::walk_dir(&Path(".")) |p| {
|
|
if p.filetype() == Some(~".rc") {
|
|
vec::push(cratefiles, *p);
|
|
}
|
|
}
|
|
|
|
if vec::is_empty(cratefiles) {
|
|
fail ~"this doesn't look like a rust package (no .rc files)";
|
|
}
|
|
|
|
for cratefiles.each |cf| {
|
|
match load_crate(&cf) {
|
|
None => loop,
|
|
Some(crate) => {
|
|
for crate.deps.each |query| {
|
|
// FIXME (#1356): handle cyclic dependencies
|
|
// (n.b. #1356 says "Cyclic dependency is an error
|
|
// condition")
|
|
|
|
let wd = get_temp_workdir(c);
|
|
install_query(c, &wd, query);
|
|
}
|
|
|
|
os::change_dir(path);
|
|
|
|
if c.opts.test {
|
|
test_one_crate(c, path, &cf);
|
|
}
|
|
install_one_crate(c, path, &cf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn install_git(c: &cargo, wd: &Path, url: ~str, reference: Option<~str>) {
|
|
run::program_output(~"git", ~[~"clone", url, wd.to_str()]);
|
|
if option::is_some(reference) {
|
|
let r = option::get(reference);
|
|
os::change_dir(wd);
|
|
run::run_program(~"git", ~[~"checkout", r]);
|
|
}
|
|
|
|
install_source(c, wd);
|
|
}
|
|
|
|
fn install_curl(c: &cargo, wd: &Path, url: ~str) {
|
|
let tarpath = wd.push("pkg.tar");
|
|
let p = run::program_output(~"curl", ~[~"-f", ~"-s", ~"-o",
|
|
tarpath.to_str(), url]);
|
|
if p.status != 0 {
|
|
fail fmt!("fetch of %s failed: %s", url, p.err);
|
|
}
|
|
run::run_program(~"tar", ~[~"-x", ~"--strip-components=1",
|
|
~"-C", wd.to_str(),
|
|
~"-f", tarpath.to_str()]);
|
|
install_source(c, wd);
|
|
}
|
|
|
|
fn install_file(c: &cargo, wd: &Path, path: &Path) {
|
|
run::program_output(~"tar", ~[~"-x", ~"--strip-components=1",
|
|
~"-C", wd.to_str(),
|
|
~"-f", path.to_str()]);
|
|
install_source(c, wd);
|
|
}
|
|
|
|
fn install_package(c: &cargo, src: ~str, wd: &Path, pkg: package) {
|
|
let url = copy pkg.url;
|
|
let method = match pkg.method {
|
|
~"git" => ~"git",
|
|
~"file" => ~"file",
|
|
_ => ~"curl"
|
|
};
|
|
|
|
info(fmt!("installing %s/%s via %s...", src, pkg.name, method));
|
|
|
|
match method {
|
|
~"git" => install_git(c, wd, url, copy pkg.reference),
|
|
~"file" => install_file(c, wd, &Path(url)),
|
|
~"curl" => install_curl(c, wd, copy url),
|
|
_ => ()
|
|
}
|
|
}
|
|
|
|
fn cargo_suggestion(c: &cargo, fallback: fn())
|
|
{
|
|
if c.sources.size() == 0u {
|
|
error(~"no sources defined - you may wish to run " +
|
|
~"`cargo init`");
|
|
return;
|
|
}
|
|
fallback();
|
|
}
|
|
|
|
fn install_uuid(c: &cargo, wd: &Path, uuid: ~str) {
|
|
let mut ps = ~[];
|
|
for_each_package(c, |s, p| {
|
|
if p.uuid == uuid {
|
|
vec::grow(ps, 1u, (s.name, copy p));
|
|
}
|
|
});
|
|
if vec::len(ps) == 1u {
|
|
let (sname, p) = copy ps[0];
|
|
install_package(c, sname, wd, p);
|
|
return;
|
|
} else if vec::len(ps) == 0u {
|
|
cargo_suggestion(c, || {
|
|
error(~"can't find package: " + uuid);
|
|
});
|
|
return;
|
|
}
|
|
error(~"found multiple packages:");
|
|
for ps.each |elt| {
|
|
let (sname,p) = copy elt;
|
|
info(~" " + sname + ~"/" + p.uuid + ~" (" + p.name + ~")");
|
|
}
|
|
}
|
|
|
|
fn install_named(c: &cargo, wd: &Path, name: ~str) {
|
|
let mut ps = ~[];
|
|
for_each_package(c, |s, p| {
|
|
if p.name == name {
|
|
vec::grow(ps, 1u, (s.name, copy p));
|
|
}
|
|
});
|
|
if vec::len(ps) == 1u {
|
|
let (sname, p) = copy ps[0];
|
|
install_package(c, sname, wd, p);
|
|
return;
|
|
} else if vec::len(ps) == 0u {
|
|
cargo_suggestion(c, || {
|
|
error(~"can't find package: " + name);
|
|
});
|
|
return;
|
|
}
|
|
error(~"found multiple packages:");
|
|
for ps.each |elt| {
|
|
let (sname,p) = copy elt;
|
|
info(~" " + sname + ~"/" + p.uuid + ~" (" + p.name + ~")");
|
|
}
|
|
}
|
|
|
|
fn install_uuid_specific(c: &cargo, wd: &Path, src: ~str, uuid: ~str) {
|
|
match c.sources.find(src) {
|
|
Some(s) => {
|
|
for s.packages.each |p| {
|
|
if p.uuid == uuid {
|
|
install_package(c, src, wd, p);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
error(~"can't find package: " + src + ~"/" + uuid);
|
|
}
|
|
|
|
fn install_named_specific(c: &cargo, wd: &Path, src: ~str, name: ~str) {
|
|
match c.sources.find(src) {
|
|
Some(s) => {
|
|
for s.packages.each |p| {
|
|
if p.name == name {
|
|
install_package(c, src, wd, p);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
error(~"can't find package: " + src + ~"/" + name);
|
|
}
|
|
|
|
fn cmd_uninstall(c: &cargo) {
|
|
if vec::len(c.opts.free) < 3u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let lib = &c.libdir;
|
|
let bin = &c.bindir;
|
|
let target = c.opts.free[2u];
|
|
|
|
// FIXME (#2662): needs stronger pattern matching
|
|
// FIXME (#2662): needs to uninstall from a specified location in a
|
|
// cache instead of looking for it (binaries can be uninstalled by
|
|
// name only)
|
|
|
|
fn try_uninstall(p: &Path) -> bool {
|
|
if os::remove_file(p) {
|
|
info(~"uninstalled: '" + p.to_str() + ~"'");
|
|
true
|
|
} else {
|
|
error(~"could not uninstall: '" +
|
|
p.to_str() + ~"'");
|
|
false
|
|
}
|
|
}
|
|
|
|
if is_uuid(target) {
|
|
for os::list_dir(lib).each |file| {
|
|
match str::find_str(file, ~"-" + target + ~"-") {
|
|
Some(_) => if !try_uninstall(&lib.push(file)) { return },
|
|
None => ()
|
|
}
|
|
}
|
|
error(~"can't find package with uuid: " + target);
|
|
} else {
|
|
for os::list_dir(lib).each |file| {
|
|
match str::find_str(file, ~"lib" + target + ~"-") {
|
|
Some(_) => if !try_uninstall(&lib.push(file)) { return },
|
|
None => ()
|
|
}
|
|
}
|
|
for os::list_dir(bin).each |file| {
|
|
match str::find_str(file, target) {
|
|
Some(_) => if !try_uninstall(&lib.push(file)) { return },
|
|
None => ()
|
|
}
|
|
}
|
|
|
|
error(~"can't find package with name: " + target);
|
|
}
|
|
}
|
|
|
|
fn install_query(c: &cargo, wd: &Path, target: ~str) {
|
|
match c.dep_cache.find(target) {
|
|
Some(inst) => {
|
|
if inst {
|
|
return;
|
|
}
|
|
}
|
|
None => ()
|
|
}
|
|
|
|
c.dep_cache.insert(target, true);
|
|
|
|
if is_archive_path(target) {
|
|
install_file(c, wd, &Path(target));
|
|
return;
|
|
} else if is_git_url(target) {
|
|
let reference = if c.opts.free.len() >= 4u {
|
|
Some(c.opts.free[3u])
|
|
} else {
|
|
None
|
|
};
|
|
install_git(c, wd, target, reference);
|
|
} else if !valid_pkg_name(target) && has_archive_extension(target) {
|
|
install_curl(c, wd, target);
|
|
return;
|
|
} else {
|
|
let mut ps = copy target;
|
|
|
|
match str::find_char(ps, '/') {
|
|
option::Some(idx) => {
|
|
let source = str::slice(ps, 0u, idx);
|
|
ps = str::slice(ps, idx + 1u, str::len(ps));
|
|
if is_uuid(ps) {
|
|
install_uuid_specific(c, wd, source, ps);
|
|
} else {
|
|
install_named_specific(c, wd, source, ps);
|
|
}
|
|
}
|
|
option::None => {
|
|
if is_uuid(ps) {
|
|
install_uuid(c, wd, ps);
|
|
} else {
|
|
install_named(c, wd, ps);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME (#2662): 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 get_temp_workdir(c: &cargo) -> Path {
|
|
match tempfile::mkdtemp(&c.workdir, "cargo") {
|
|
Some(wd) => wd,
|
|
None => fail fmt!("needed temp dir: %s",
|
|
c.workdir.to_str())
|
|
}
|
|
}
|
|
|
|
fn cmd_install(c: &cargo) unsafe {
|
|
let wd = get_temp_workdir(c);
|
|
|
|
if vec::len(c.opts.free) == 2u {
|
|
let cwd = os::getcwd();
|
|
let status = run::run_program(~"cp", ~[~"-R", cwd.to_str(),
|
|
wd.to_str()]);
|
|
|
|
if status != 0 {
|
|
fail fmt!("could not copy directory: %s", cwd.to_str());
|
|
}
|
|
|
|
install_source(c, &wd);
|
|
return;
|
|
}
|
|
|
|
sync(c);
|
|
|
|
let query = c.opts.free[2];
|
|
c.current_install = query.to_str();
|
|
|
|
install_query(c, &wd, query);
|
|
}
|
|
|
|
fn sync(c: &cargo) {
|
|
for c.sources.each_key |k| {
|
|
let mut s = c.sources.get(k);
|
|
sync_one(c, s);
|
|
c.sources.insert(k, s);
|
|
}
|
|
}
|
|
|
|
fn sync_one_file(c: &cargo, dir: &Path, src: source) -> bool {
|
|
let name = src.name;
|
|
let srcfile = dir.push("source.json.new");
|
|
let destsrcfile = dir.push("source.json");
|
|
let pkgfile = dir.push("packages.json.new");
|
|
let destpkgfile = dir.push("packages.json");
|
|
let keyfile = dir.push("key.gpg");
|
|
let srcsigfile = dir.push("source.json.sig");
|
|
let sigfile = dir.push("packages.json.sig");
|
|
let url = Path(src.url);
|
|
let mut has_src_file = false;
|
|
|
|
if !os::copy_file(&url.push("packages.json"), &pkgfile) {
|
|
error(fmt!("fetch for source %s (url %s) failed",
|
|
name, url.to_str()));
|
|
return false;
|
|
}
|
|
|
|
if os::copy_file(&url.push("source.json"), &srcfile) {
|
|
has_src_file = false;
|
|
}
|
|
|
|
os::copy_file(&url.push("source.json.sig"), &srcsigfile);
|
|
os::copy_file(&url.push("packages.json.sig"), &sigfile);
|
|
|
|
match copy src.key {
|
|
Some(u) => {
|
|
let p = run::program_output(~"curl",
|
|
~[~"-f", ~"-s",
|
|
~"-o", keyfile.to_str(), u]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (key %s) failed", name, u));
|
|
return false;
|
|
}
|
|
pgp::add(&c.root, &keyfile);
|
|
}
|
|
_ => ()
|
|
}
|
|
match (src.key, src.keyfp) {
|
|
(Some(_), Some(f)) => {
|
|
let r = pgp::verify(&c.root, &pkgfile, &sigfile, f);
|
|
|
|
if !r {
|
|
error(fmt!("signature verification failed for source %s",
|
|
name));
|
|
return false;
|
|
}
|
|
|
|
if has_src_file {
|
|
let e = pgp::verify(&c.root, &srcfile, &srcsigfile, f);
|
|
|
|
if !e {
|
|
error(fmt!("signature verification failed for source %s",
|
|
name));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
|
|
copy_warn(&pkgfile, &destpkgfile);
|
|
|
|
if has_src_file {
|
|
copy_warn(&srcfile, &destsrcfile);
|
|
}
|
|
|
|
os::remove_file(&keyfile);
|
|
os::remove_file(&srcfile);
|
|
os::remove_file(&srcsigfile);
|
|
os::remove_file(&pkgfile);
|
|
os::remove_file(&sigfile);
|
|
|
|
info(fmt!("synced source: %s", name));
|
|
|
|
return true;
|
|
}
|
|
|
|
fn sync_one_git(c: &cargo, dir: &Path, src: source) -> bool {
|
|
let name = src.name;
|
|
let srcfile = dir.push("source.json");
|
|
let pkgfile = dir.push("packages.json");
|
|
let keyfile = dir.push("key.gpg");
|
|
let srcsigfile = dir.push("source.json.sig");
|
|
let sigfile = dir.push("packages.json.sig");
|
|
let url = src.url;
|
|
|
|
fn rollback(name: ~str, dir: &Path, insecure: bool) {
|
|
fn msg(name: ~str, insecure: bool) {
|
|
error(fmt!("could not rollback source: %s", name));
|
|
|
|
if insecure {
|
|
warn(~"a past security check failed on source " +
|
|
name + ~" and rolling back the source failed -"
|
|
+ ~" this source may be compromised");
|
|
}
|
|
}
|
|
|
|
if !os::change_dir(dir) {
|
|
msg(name, insecure);
|
|
}
|
|
else {
|
|
let p = run::program_output(~"git", ~[~"reset", ~"--hard",
|
|
~"HEAD@{1}"]);
|
|
|
|
if p.status != 0 {
|
|
msg(name, insecure);
|
|
}
|
|
}
|
|
}
|
|
|
|
if !os::path_exists(&dir.push(".git")) {
|
|
let p = run::program_output(~"git", ~[~"clone", url, dir.to_str()]);
|
|
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (url %s) failed", name, url));
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if !os::change_dir(dir) {
|
|
error(fmt!("fetch for source %s (url %s) failed", name, url));
|
|
return false;
|
|
}
|
|
|
|
let p = run::program_output(~"git", ~[~"pull"]);
|
|
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (url %s) failed", name, url));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
let has_src_file = os::path_exists(&srcfile);
|
|
|
|
match copy src.key {
|
|
Some(u) => {
|
|
let p = run::program_output(~"curl",
|
|
~[~"-f", ~"-s",
|
|
~"-o", keyfile.to_str(), u]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (key %s) failed", name, u));
|
|
rollback(name, dir, false);
|
|
return false;
|
|
}
|
|
pgp::add(&c.root, &keyfile);
|
|
}
|
|
_ => ()
|
|
}
|
|
match (src.key, src.keyfp) {
|
|
(Some(_), Some(f)) => {
|
|
let r = pgp::verify(&c.root, &pkgfile, &sigfile, f);
|
|
|
|
if !r {
|
|
error(fmt!("signature verification failed for source %s",
|
|
name));
|
|
rollback(name, dir, false);
|
|
return false;
|
|
}
|
|
|
|
if has_src_file {
|
|
let e = pgp::verify(&c.root, &srcfile, &srcsigfile, f);
|
|
|
|
if !e {
|
|
error(fmt!("signature verification failed for source %s",
|
|
name));
|
|
rollback(name, dir, false);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
|
|
os::remove_file(&keyfile);
|
|
|
|
info(fmt!("synced source: %s", name));
|
|
|
|
return true;
|
|
}
|
|
|
|
fn sync_one_curl(c: &cargo, dir: &Path, src: source) -> bool {
|
|
let name = src.name;
|
|
let srcfile = dir.push("source.json.new");
|
|
let destsrcfile = dir.push("source.json");
|
|
let pkgfile = dir.push("packages.json.new");
|
|
let destpkgfile = dir.push("packages.json");
|
|
let keyfile = dir.push("key.gpg");
|
|
let srcsigfile = dir.push("source.json.sig");
|
|
let sigfile = dir.push("packages.json.sig");
|
|
let mut url = src.url;
|
|
let smart = !str::ends_with(src.url, ~"packages.json");
|
|
let mut has_src_file = false;
|
|
|
|
if smart {
|
|
url += ~"/packages.json";
|
|
}
|
|
|
|
let p = run::program_output(~"curl",
|
|
~[~"-f", ~"-s",
|
|
~"-o", pkgfile.to_str(), url]);
|
|
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (url %s) failed", name, url));
|
|
return false;
|
|
}
|
|
if smart {
|
|
url = src.url + ~"/source.json";
|
|
let p =
|
|
run::program_output(~"curl",
|
|
~[~"-f", ~"-s",
|
|
~"-o", srcfile.to_str(), url]);
|
|
|
|
if p.status == 0 {
|
|
has_src_file = true;
|
|
}
|
|
}
|
|
|
|
match copy src.key {
|
|
Some(u) => {
|
|
let p = run::program_output(~"curl",
|
|
~[~"-f", ~"-s",
|
|
~"-o", keyfile.to_str(), u]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (key %s) failed", name, u));
|
|
return false;
|
|
}
|
|
pgp::add(&c.root, &keyfile);
|
|
}
|
|
_ => ()
|
|
}
|
|
match (src.key, src.keyfp) {
|
|
(Some(_), Some(f)) => {
|
|
if smart {
|
|
url = src.url + ~"/packages.json.sig";
|
|
}
|
|
else {
|
|
url = src.url + ~".sig";
|
|
}
|
|
|
|
let mut p = run::program_output(~"curl",
|
|
~[~"-f", ~"-s", ~"-o",
|
|
sigfile.to_str(), url]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (sig %s) failed", name, url));
|
|
return false;
|
|
}
|
|
|
|
let r = pgp::verify(&c.root, &pkgfile, &sigfile, f);
|
|
|
|
if !r {
|
|
error(fmt!("signature verification failed for source %s",
|
|
name));
|
|
return false;
|
|
}
|
|
|
|
if smart && has_src_file {
|
|
url = src.url + ~"/source.json.sig";
|
|
|
|
p = run::program_output(~"curl",
|
|
~[~"-f", ~"-s", ~"-o",
|
|
srcsigfile.to_str(), url]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch for source %s (sig %s) failed",
|
|
name, url));
|
|
return false;
|
|
}
|
|
|
|
let e = pgp::verify(&c.root, &srcfile, &srcsigfile, f);
|
|
|
|
if !e {
|
|
error(~"signature verification failed for " +
|
|
~"source " + name);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
_ => ()
|
|
}
|
|
|
|
copy_warn(&pkgfile, &destpkgfile);
|
|
|
|
if smart && has_src_file {
|
|
copy_warn(&srcfile, &destsrcfile);
|
|
}
|
|
|
|
os::remove_file(&keyfile);
|
|
os::remove_file(&srcfile);
|
|
os::remove_file(&srcsigfile);
|
|
os::remove_file(&pkgfile);
|
|
os::remove_file(&sigfile);
|
|
|
|
info(fmt!("synced source: %s", name));
|
|
|
|
return true;
|
|
}
|
|
|
|
fn sync_one(c: &cargo, src: source) {
|
|
let name = src.name;
|
|
let dir = c.sourcedir.push(name);
|
|
|
|
info(fmt!("syncing source: %s...", name));
|
|
|
|
need_dir(&dir);
|
|
|
|
let result = match src.method {
|
|
~"git" => sync_one_git(c, &dir, src),
|
|
~"file" => sync_one_file(c, &dir, src),
|
|
_ => sync_one_curl(c, &dir, src)
|
|
};
|
|
|
|
if result {
|
|
load_source_info(c, src);
|
|
load_source_packages(c, src);
|
|
}
|
|
}
|
|
|
|
fn cmd_init(c: &cargo) {
|
|
let srcurl = ~"http://www.rust-lang.org/cargo/sources.json";
|
|
let sigurl = ~"http://www.rust-lang.org/cargo/sources.json.sig";
|
|
|
|
let srcfile = c.root.push("sources.json.new");
|
|
let sigfile = c.root.push("sources.json.sig");
|
|
let destsrcfile = c.root.push("sources.json");
|
|
|
|
let p =
|
|
run::program_output(~"curl", ~[~"-f", ~"-s",
|
|
~"-o", srcfile.to_str(), srcurl]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch of sources.json failed: %s", p.out));
|
|
return;
|
|
}
|
|
|
|
let p =
|
|
run::program_output(~"curl", ~[~"-f", ~"-s",
|
|
~"-o", sigfile.to_str(), sigurl]);
|
|
if p.status != 0 {
|
|
error(fmt!("fetch of sources.json.sig failed: %s", p.out));
|
|
return;
|
|
}
|
|
|
|
let r = pgp::verify(&c.root, &srcfile, &sigfile,
|
|
pgp::signing_key_fp());
|
|
if !r {
|
|
error(fmt!("signature verification failed for '%s'",
|
|
srcfile.to_str()));
|
|
return;
|
|
}
|
|
|
|
copy_warn(&srcfile, &destsrcfile);
|
|
os::remove_file(&srcfile);
|
|
os::remove_file(&sigfile);
|
|
|
|
info(fmt!("initialized .cargo in %s", c.root.to_str()));
|
|
}
|
|
|
|
fn print_pkg(s: source, p: package) {
|
|
let mut m = s.name + ~"/" + p.name + ~" (" + p.uuid + ~")";
|
|
if vec::len(p.tags) > 0u {
|
|
m = m + ~" [" + str::connect(p.tags, ~", ") + ~"]";
|
|
}
|
|
info(m);
|
|
if p.description != ~"" {
|
|
print(~" >> " + p.description + ~"\n")
|
|
}
|
|
}
|
|
|
|
fn print_source(s: source) {
|
|
info(s.name + ~" (" + s.url + ~")");
|
|
|
|
let pks = sort::merge_sort(sys::shape_lt, s.packages.get());
|
|
let l = vec::len(pks);
|
|
|
|
print(io::with_str_writer(|writer| {
|
|
let mut list = ~" >> ";
|
|
|
|
do vec::iteri(pks) |i, pk| {
|
|
if str::len(list) > 78u {
|
|
writer.write_line(list);
|
|
list = ~" >> ";
|
|
}
|
|
list += pk.name + (if l - 1u == i { ~"" } else { ~", " });
|
|
}
|
|
|
|
writer.write_line(list);
|
|
}));
|
|
}
|
|
|
|
fn cmd_list(c: &cargo) {
|
|
sync(c);
|
|
|
|
if vec::len(c.opts.free) >= 3u {
|
|
do vec::iter_between(c.opts.free, 2u, vec::len(c.opts.free)) |name| {
|
|
if !valid_pkg_name(name) {
|
|
error(fmt!("'%s' is an invalid source name", name));
|
|
} else {
|
|
match c.sources.find(name) {
|
|
Some(source) => {
|
|
print_source(source);
|
|
}
|
|
None => {
|
|
error(fmt!("no such source: %s", name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for c.sources.each_value |v| {
|
|
print_source(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn cmd_search(c: &cargo) {
|
|
if vec::len(c.opts.free) < 3u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
sync(c);
|
|
|
|
let mut n = 0;
|
|
let name = c.opts.free[2];
|
|
let tags = vec::slice(c.opts.free, 3u, vec::len(c.opts.free));
|
|
for_each_package(c, |s, p| {
|
|
if (str::contains(p.name, name) || name == ~"*") &&
|
|
vec::all(tags, |t| vec::contains(p.tags, t) ) {
|
|
print_pkg(s, p);
|
|
n += 1;
|
|
}
|
|
});
|
|
info(fmt!("found %d packages", n));
|
|
}
|
|
|
|
fn install_to_dir(srcfile: &Path, destdir: &Path) {
|
|
let newfile = destdir.push(option::get(srcfile.filename()));
|
|
|
|
let status = run::run_program(~"cp", ~[~"-r", srcfile.to_str(),
|
|
newfile.to_str()]);
|
|
if status == 0 {
|
|
info(fmt!("installed: '%s'", newfile.to_str()));
|
|
} else {
|
|
error(fmt!("could not install: '%s'", newfile.to_str()));
|
|
}
|
|
}
|
|
|
|
fn dump_cache(c: &cargo) {
|
|
need_dir(&c.root);
|
|
|
|
let out = c.root.push("cache.json");
|
|
let _root = json::Dict(map::str_hash());
|
|
|
|
if os::path_exists(&out) {
|
|
copy_warn(&out, &c.root.push("cache.json.old"));
|
|
}
|
|
}
|
|
fn dump_sources(c: &cargo) {
|
|
if c.sources.size() < 1u {
|
|
return;
|
|
}
|
|
|
|
need_dir(&c.root);
|
|
|
|
let out = c.root.push("sources.json");
|
|
|
|
if os::path_exists(&out) {
|
|
copy_warn(&out, &c.root.push("sources.json.old"));
|
|
}
|
|
|
|
match io::buffered_file_writer(&out) {
|
|
result::Ok(writer) => {
|
|
let hash = map::str_hash();
|
|
let root = json::Dict(hash);
|
|
|
|
for c.sources.each |k, v| {
|
|
let chash = map::str_hash();
|
|
let child = json::Dict(chash);
|
|
|
|
chash.insert(~"url", json::String(@v.url));
|
|
chash.insert(~"method", json::String(@v.method));
|
|
|
|
match copy v.key {
|
|
Some(key) => {
|
|
chash.insert(~"key", json::String(@key));
|
|
}
|
|
_ => ()
|
|
}
|
|
match copy v.keyfp {
|
|
Some(keyfp) => {
|
|
chash.insert(~"keyfp", json::String(@keyfp));
|
|
}
|
|
_ => ()
|
|
}
|
|
|
|
hash.insert(k, child);
|
|
}
|
|
|
|
writer.write_str(json::to_str(root));
|
|
}
|
|
result::Err(e) => {
|
|
error(fmt!("could not dump sources: %s", e));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn copy_warn(srcfile: &Path, destfile: &Path) {
|
|
if !os::copy_file(srcfile, destfile) {
|
|
warn(fmt!("copying %s to %s failed",
|
|
srcfile.to_str(), destfile.to_str()));
|
|
}
|
|
}
|
|
|
|
fn cmd_sources(c: &cargo) {
|
|
if vec::len(c.opts.free) < 3u {
|
|
for c.sources.each_value |v| {
|
|
info(fmt!("%s (%s) via %s",
|
|
v.name, v.url, v.method));
|
|
}
|
|
return;
|
|
}
|
|
|
|
let action = c.opts.free[2u];
|
|
|
|
match action {
|
|
~"clear" => {
|
|
for c.sources.each_key |k| {
|
|
c.sources.remove(k);
|
|
}
|
|
|
|
info(~"cleared sources");
|
|
}
|
|
~"add" => {
|
|
if vec::len(c.opts.free) < 5u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let name = c.opts.free[3u];
|
|
let url = c.opts.free[4u];
|
|
|
|
if !valid_pkg_name(name) {
|
|
error(fmt!("'%s' is an invalid source name", name));
|
|
return;
|
|
}
|
|
|
|
if c.sources.contains_key(name) {
|
|
error(fmt!("source already exists: %s", name));
|
|
} else {
|
|
c.sources.insert(name, @{
|
|
name: name,
|
|
mut url: url,
|
|
mut method: assume_source_method(url),
|
|
mut key: None,
|
|
mut keyfp: None,
|
|
packages: DVec()
|
|
});
|
|
info(fmt!("added source: %s", name));
|
|
}
|
|
}
|
|
~"remove" => {
|
|
if vec::len(c.opts.free) < 4u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let name = c.opts.free[3u];
|
|
|
|
if !valid_pkg_name(name) {
|
|
error(fmt!("'%s' is an invalid source name", name));
|
|
return;
|
|
}
|
|
|
|
if c.sources.contains_key(name) {
|
|
c.sources.remove(name);
|
|
info(fmt!("removed source: %s", name));
|
|
} else {
|
|
error(fmt!("no such source: %s", name));
|
|
}
|
|
}
|
|
~"set-url" => {
|
|
if vec::len(c.opts.free) < 5u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let name = c.opts.free[3u];
|
|
let url = c.opts.free[4u];
|
|
|
|
if !valid_pkg_name(name) {
|
|
error(fmt!("'%s' is an invalid source name", name));
|
|
return;
|
|
}
|
|
|
|
match c.sources.find(name) {
|
|
Some(source) => {
|
|
let old = copy source.url;
|
|
let method = assume_source_method(url);
|
|
|
|
source.url = url;
|
|
source.method = method;
|
|
|
|
c.sources.insert(name, source);
|
|
|
|
info(fmt!("changed source url: '%s' to '%s'", old, url));
|
|
}
|
|
None => {
|
|
error(fmt!("no such source: %s", name));
|
|
}
|
|
}
|
|
}
|
|
~"set-method" => {
|
|
if vec::len(c.opts.free) < 5u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let name = c.opts.free[3u];
|
|
let method = c.opts.free[4u];
|
|
|
|
if !valid_pkg_name(name) {
|
|
error(fmt!("'%s' is an invalid source name", name));
|
|
return;
|
|
}
|
|
|
|
match c.sources.find(name) {
|
|
Some(source) => {
|
|
let old = copy source.method;
|
|
|
|
source.method = match method {
|
|
~"git" => ~"git",
|
|
~"file" => ~"file",
|
|
_ => ~"curl"
|
|
};
|
|
|
|
c.sources.insert(name, source);
|
|
|
|
info(fmt!("changed source method: '%s' to '%s'", old,
|
|
method));
|
|
}
|
|
None => {
|
|
error(fmt!("no such source: %s", name));
|
|
}
|
|
}
|
|
}
|
|
~"rename" => {
|
|
if vec::len(c.opts.free) < 5u {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let name = c.opts.free[3u];
|
|
let newn = c.opts.free[4u];
|
|
|
|
if !valid_pkg_name(name) {
|
|
error(fmt!("'%s' is an invalid source name", name));
|
|
return;
|
|
}
|
|
if !valid_pkg_name(newn) {
|
|
error(fmt!("'%s' is an invalid source name", newn));
|
|
return;
|
|
}
|
|
|
|
match c.sources.find(name) {
|
|
Some(source) => {
|
|
c.sources.remove(name);
|
|
c.sources.insert(newn, source);
|
|
info(fmt!("renamed source: %s to %s", name, newn));
|
|
}
|
|
None => {
|
|
error(fmt!("no such source: %s", name));
|
|
}
|
|
}
|
|
}
|
|
_ => cmd_usage()
|
|
}
|
|
}
|
|
|
|
fn cmd_usage() {
|
|
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 {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
if o.help {
|
|
match 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()
|
|
}
|
|
return;
|
|
}
|
|
if o.free[1] == ~"usage" {
|
|
cmd_usage();
|
|
return;
|
|
}
|
|
|
|
let mut c = configure(o);
|
|
let home = c.root;
|
|
let first_time = os::path_exists(&home.push("sources.json"));
|
|
|
|
if !first_time && o.free[1] != ~"init" {
|
|
cmd_init(&c);
|
|
|
|
// FIXME (#2662): shouldn't need to reconfigure
|
|
c = configure(o);
|
|
}
|
|
|
|
let c = &move c;
|
|
|
|
match o.free[1] {
|
|
~"init" => cmd_init(c),
|
|
~"install" => cmd_install(c),
|
|
~"uninstall" => cmd_uninstall(c),
|
|
~"list" => cmd_list(c),
|
|
~"search" => cmd_search(c),
|
|
~"sources" => cmd_sources(c),
|
|
_ => cmd_usage()
|
|
}
|
|
|
|
dump_cache(c);
|
|
dump_sources(c);
|
|
}
|