Implement cargo-update

This commit is contained in:
Alex Crichton 2014-07-31 15:19:20 -07:00
parent 07c3c89c6f
commit 620b2fe1b5
12 changed files with 174 additions and 54 deletions

View file

@ -83,5 +83,9 @@ test = false
name = "cargo-generate-lockfile"
test = false
[[bin]]
name = "cargo-update"
test = false
[[test]]
name = "tests"

View file

@ -26,7 +26,7 @@ Options:
-j N, --jobs N The number of jobs to run in parallel
--release Build artifacts in release mode, with optimizations
--target TRIPLE Build for the target triple
-u, --update-remotes Update all remote packages before compiling
-u, --update-remotes Deprecated option, use `cargo update` instead
--manifest-path PATH Path to the manifest to compile
-v, --verbose Use verbose output
", flag_jobs: Option<uint>, flag_target: Option<String>,

View file

@ -23,7 +23,7 @@ Options:
-h, --help Print this message
--no-deps Don't build documentation for dependencies
-j N, --jobs N The number of jobs to run in parallel
-u, --update-remotes Update all remote packages before compiling
-u, --update-remotes Deprecated option, use `cargo update` instead
--manifest-path PATH Path to the manifest to document
-v, --verbose Use verbose output

View file

@ -22,7 +22,7 @@ Usage:
Options:
-h, --help Print this message
-j N, --jobs N The number of jobs to run in parallel
-u, --update-remotes Update all remote packages before compiling
-u, --update-remotes Deprecated option, use `cargo update` instead
--manifest-path PATH Path to the manifest to execute
-v, --verbose Use verbose output

View file

@ -23,7 +23,7 @@ Usage:
Options:
-h, --help Print this message
-j N, --jobs N The number of jobs to run in parallel
-u, --update-remotes Update all remote packages before compiling
-u, --update-remotes Deprecated option, use `cargo update` instead
--manifest-path PATH Path to the manifest to build tests for
-v, --verbose Use verbose output

51
src/bin/cargo-update.rs Normal file
View file

@ -0,0 +1,51 @@
#![feature(phase)]
extern crate serialize;
extern crate cargo;
extern crate docopt;
#[phase(plugin)] extern crate docopt_macros;
#[phase(plugin, link)] extern crate log;
use std::os;
use cargo::ops;
use cargo::{execute_main_without_stdin};
use cargo::core::MultiShell;
use cargo::util::{CliResult, CliError};
use cargo::util::important_paths::find_root_manifest_for_cwd;
docopt!(Options, "
Update dependencies as recorded in the local lock file.
Usage:
cargo-update [options] [<name>]
Options:
-h, --help Print this message
--manifest-path PATH Path to the manifest to compile
-v, --verbose Use verbose output
This command requires that a `Cargo.lock` already exists as generated by
`cargo build` or related commands.
If <name> is specified, then a conservative update of the lockfile will be
performed. This means that only the dependency <name> (and all of its transitive
dependencies) will be updated. All other dependencies will remain locked at
their currently recorded versions.
If <name> is not specified, then all dependencies will be re-resolved and
updated.
", flag_manifest_path: Option<String>, arg_name: Option<String>)
fn main() {
execute_main_without_stdin(execute, false);
}
fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
debug!("executing; cmd=cargo-update; args={}", os::args());
shell.set_verbose(options.flag_verbose);
let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path));
ops::update_lockfile(&root, shell, options.arg_name)
.map(|_| None).map_err(|err| CliError::from_boxed(err, 101))
}

View file

@ -41,6 +41,7 @@ Some common cargo commands are:
new Create a new cargo project
run Build and execute src/main.rs
test Run the tests
update Update dependencies listed in Cargo.lock
See 'cargo help <command>' for more information on a specific command.
")

View file

@ -24,16 +24,14 @@
use std::os;
use std::collections::HashMap;
use std::io::File;
use serialize::Decodable;
use rstoml = toml;
use core::registry::PackageRegistry;
use core::{MultiShell, Source, SourceId, PackageSet, Target, PackageId, Resolve, resolver};
use core::{MultiShell, Source, SourceId, PackageSet, Target, PackageId};
use core::resolver;
use ops;
use sources::{PathSource};
use util::config::{Config, ConfigValue};
use util::{CargoResult, Wrap, config, internal, human, ChainError, toml};
use util::{CargoResult, Wrap, config, internal, human, ChainError};
use util::profile;
pub struct CompileOptions<'a> {
@ -51,6 +49,11 @@ pub fn compile(manifest_path: &Path,
log!(4, "compile; manifest-path={}", manifest_path.display());
if options.update {
return Err(human("The -u flag has been deprecated, please use the \
`cargo update` command instead"));
}
let mut source = PathSource::for_path(&manifest_path.dir_path());
try!(source.update());
@ -76,7 +79,7 @@ pub fn compile(manifest_path: &Path,
let mut registry = PackageRegistry::new(&mut config);
let resolved = match try!(load_lockfile(&lockfile, source_id)) {
let resolved = match try!(ops::load_lockfile(&lockfile, source_id)) {
Some(r) => {
try!(registry.add_sources(r.iter().map(|p| {
p.get_source_id().clone()
@ -145,21 +148,6 @@ pub fn compile(manifest_path: &Path,
Ok(test_executables)
}
fn load_lockfile(path: &Path, sid: &SourceId) -> CargoResult<Option<Resolve>> {
// If there is no lockfile, return none.
let mut f = match File::open(path) {
Ok(f) => f,
Err(_) => return Ok(None)
};
let s = try!(f.read_to_string());
let table = rstoml::Table(try!(toml::parse(s.as_slice(), path)));
let mut d = rstoml::Decoder::new(table);
let v: resolver::EncodableResolve = Decodable::decode(&mut d).unwrap();
Ok(Some(try!(v.to_resolve(sid))))
}
fn source_ids_from_config(configs: &HashMap<String, config::ConfigValue>,
cur_path: Path) -> CargoResult<Vec<SourceId>> {
debug!("loaded config; configs={}", configs);

View file

@ -1,13 +1,17 @@
#![warn(warnings)]
use std::collections::HashSet;
use std::io::File;
use serialize::Encodable;
use toml::{mod, Encoder};
use serialize::{Encodable, Decodable};
use toml::Encoder;
use rstoml = toml;
use core::registry::PackageRegistry;
use core::{MultiShell, Source, Resolve, resolver, Package};
use core::{MultiShell, Source, Resolve, resolver, Package, SourceId};
use core::PackageId;
use sources::{PathSource};
use util::config::{Config};
use util::{CargoResult};
use util::{CargoResult, toml, human};
pub fn generate_lockfile(manifest_path: &Path,
shell: &mut MultiShell,
@ -43,9 +47,80 @@ pub fn write_resolve(pkg: &Package, resolve: &Resolve) -> CargoResult<()> {
let mut e = Encoder::new();
resolve.encode(&mut e).unwrap();
let out = toml::Table(e.toml).to_string();
let out = rstoml::Table(e.toml).to_string();
let loc = pkg.get_root().join("Cargo.lock");
try!(File::create(&loc).write_str(out.as_slice()));
Ok(())
}
pub fn update_lockfile(manifest_path: &Path,
shell: &mut MultiShell,
to_update: Option<String>) -> CargoResult<()> {
let mut source = PathSource::for_path(&manifest_path.dir_path());
try!(source.update());
let package = try!(source.get_root_package());
let lockfile = package.get_root().join("Cargo.lock");
let source_id = package.get_package_id().get_source_id();
let resolve = match try!(load_lockfile(&lockfile, source_id)) {
Some(resolve) => resolve,
None => return Err(human("A Cargo.lock must exist before it is updated"))
};
let mut config = try!(Config::new(shell, true, None, None));
let mut registry = PackageRegistry::new(&mut config);
let sources = match to_update {
Some(name) => {
let mut to_avoid = HashSet::new();
match resolve.deps(package.get_package_id()) {
Some(deps) => {
for dep in deps.filter(|d| d.get_name() == name.as_slice()) {
fill_with_deps(&resolve, dep, &mut to_avoid);
}
}
None => {}
}
resolve.iter().filter(|pkgid| !to_avoid.contains(pkgid))
.map(|pkgid| pkgid.get_source_id().clone()).collect()
}
None => package.get_source_ids(),
};
try!(registry.add_sources(sources));
let resolve = try!(resolver::resolve(package.get_package_id(),
package.get_dependencies(),
&mut registry));
try!(write_resolve(&package, &resolve));
return Ok(());
fn fill_with_deps<'a>(resolve: &'a Resolve, dep: &'a PackageId,
set: &mut HashSet<&'a PackageId>) {
if !set.insert(dep) { return }
match resolve.deps(dep) {
Some(mut deps) => {
for dep in deps {
fill_with_deps(resolve, dep, set);
}
}
None => {}
}
}
}
pub fn load_lockfile(path: &Path, sid: &SourceId) -> CargoResult<Option<Resolve>> {
// If there is no lockfile, return none.
let mut f = match File::open(path) {
Ok(f) => f,
Err(_) => return Ok(None)
};
let s = try!(f.read_to_string());
let table = rstoml::Table(try!(toml::parse(s.as_slice(), path)));
let mut d = rstoml::Decoder::new(table);
let v: resolver::EncodableResolve = Decodable::decode(&mut d).unwrap();
Ok(Some(try!(v.to_resolve(sid))))
}

View file

@ -6,6 +6,7 @@ pub use self::cargo_run::run;
pub use self::cargo_new::{new, NewOptions};
pub use self::cargo_doc::{doc, DocOptions};
pub use self::cargo_generate_lockfile::{generate_lockfile, write_resolve};
pub use self::cargo_generate_lockfile::{update_lockfile, load_lockfile};
mod cargo_clean;
mod cargo_compile;

View file

@ -161,18 +161,16 @@ impl<'a, 'b> Source for GitSource<'a, 'b> {
self.reference.as_slice());
let should_update = self.config.update_remotes() || actual_rev.is_err();
let repo = if should_update {
let (repo, actual_rev) = if should_update {
try!(self.config.shell().status("Updating",
format!("git repository `{}`", self.remote.get_location())));
log!(5, "updating git source `{}`", self.remote);
try!(self.remote.checkout(&self.db_path))
let repo = try!(self.remote.checkout(&self.db_path));
let rev = try!(repo.rev_for(self.reference.as_slice()));
(repo, rev)
} else {
self.remote.db_at(&self.db_path)
};
let actual_rev = match actual_rev {
Ok(rev) => rev,
Err(..) => try!(repo.rev_for(self.reference.as_slice())),
(self.remote.db_at(&self.db_path), actual_rev.unwrap())
};
try!(repo.copy_to(actual_rev.clone(), &self.checkout_path));

View file

@ -1,4 +1,4 @@
use std::io::{fs, File};
use std::io::File;
use support::{ProjectBuilder, ResultTest, project, execs, main_file, paths};
use support::{cargo_dir};
@ -544,11 +544,14 @@ test!(recompilation {
FRESH, git_project.root().display(),
FRESH, p.root().display())));
assert_that(p.process(cargo_dir().join("cargo-build")).arg("-u"),
execs().with_stdout(format!("{} git repository `file:{}`\n\
{} bar v0.5.0 (file:{}#[..])\n\
assert_that(p.process(cargo_dir().join("cargo-update")),
execs().with_stdout(format!("{} git repository `file:{}`",
UPDATING,
git_project.root().display())));
assert_that(p.process(cargo_dir().join("cargo-build")),
execs().with_stdout(format!("{} bar v0.5.0 (file:{}#[..])\n\
{} foo v0.5.0 (file:{})\n",
UPDATING, git_project.root().display(),
FRESH, git_project.root().display(),
FRESH, p.root().display())));
@ -558,23 +561,22 @@ test!(recompilation {
git_project.process("git").args(["commit", "-m", "test"]).exec_with_output()
.assert();
assert_that(p.process(cargo_dir().join("cargo-build")).arg("-u"),
execs().with_stdout(format!("{} git repository `file:{}`\n\
{} bar v0.5.0 (file:{}#[..])\n\
println!("compile after commit");
assert_that(p.process(cargo_dir().join("cargo-build")),
execs().with_stdout(format!("{} bar v0.5.0 (file:{}#[..])\n\
{} foo v0.5.0 (file:{})\n",
UPDATING, git_project.root().display(),
FRESH, git_project.root().display(),
FRESH, p.root().display())));
println!("one last time");
// Remove the lockfile and make sure that we update
fs::unlink(&p.root().join("Cargo.lock")).assert();
assert_that(p.process(cargo_dir().join("cargo-build")).arg("-u"),
execs().with_stdout(format!("{} git repository `file:{}`\n\
{} bar v0.5.0 (file:{}#[..])\n\
// Update the dependency and carry on!
assert_that(p.process(cargo_dir().join("cargo-update")),
execs().with_stdout(format!("{} git repository `file:{}`",
UPDATING,
git_project.root().display())));
println!("going for the last compile");
assert_that(p.process(cargo_dir().join("cargo-build")),
execs().with_stdout(format!("{} bar v0.5.0 (file:{}#[..])\n\
{} foo v0.5.0 (file:{})\n",
UPDATING, git_project.root().display(),
COMPILING, git_project.root().display(),
COMPILING, p.root().display())));
})