diff --git a/Cargo.toml b/Cargo.toml index d43c5d86e..c56dfca0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,5 +83,9 @@ test = false name = "cargo-generate-lockfile" test = false +[[bin]] +name = "cargo-update" +test = false + [[test]] name = "tests" diff --git a/src/bin/cargo-build.rs b/src/bin/cargo-build.rs index 20de1bea4..7261a20df 100644 --- a/src/bin/cargo-build.rs +++ b/src/bin/cargo-build.rs @@ -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, flag_target: Option, diff --git a/src/bin/cargo-doc.rs b/src/bin/cargo-doc.rs index afae8fc29..f5fa11f5d 100644 --- a/src/bin/cargo-doc.rs +++ b/src/bin/cargo-doc.rs @@ -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 diff --git a/src/bin/cargo-run.rs b/src/bin/cargo-run.rs index 0b4a06ce0..546e793c5 100644 --- a/src/bin/cargo-run.rs +++ b/src/bin/cargo-run.rs @@ -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 diff --git a/src/bin/cargo-test.rs b/src/bin/cargo-test.rs index defcc0305..66c22812b 100644 --- a/src/bin/cargo-test.rs +++ b/src/bin/cargo-test.rs @@ -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 diff --git a/src/bin/cargo-update.rs b/src/bin/cargo-update.rs new file mode 100644 index 000000000..7fd4f5371 --- /dev/null +++ b/src/bin/cargo-update.rs @@ -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] [] + +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 is specified, then a conservative update of the lockfile will be +performed. This means that only the dependency (and all of its transitive +dependencies) will be updated. All other dependencies will remain locked at +their currently recorded versions. + +If is not specified, then all dependencies will be re-resolved and +updated. +", flag_manifest_path: Option, arg_name: Option) + +fn main() { + execute_main_without_stdin(execute, false); +} + +fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { + 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)) +} + diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index ed711e2ce..828cfd91c 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -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 ' for more information on a specific command. ") diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 43a0214ff..c6f87c923 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -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> { - // 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, cur_path: Path) -> CargoResult> { debug!("loaded config; configs={}", configs); diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs index b792dea5f..e1c3c67d3 100644 --- a/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/cargo/ops/cargo_generate_lockfile.rs @@ -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) -> 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> { + // 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)))) +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index e47fc7cde..ba69961b9 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -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; diff --git a/src/cargo/sources/git/source.rs b/src/cargo/sources/git/source.rs index 7e7e0e848..032b36cd8 100644 --- a/src/cargo/sources/git/source.rs +++ b/src/cargo/sources/git/source.rs @@ -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)); diff --git a/tests/test_cargo_compile_git_deps.rs b/tests/test_cargo_compile_git_deps.rs index 060bfdf0e..c44996833 100644 --- a/tests/test_cargo_compile_git_deps.rs +++ b/tests/test_cargo_compile_git_deps.rs @@ -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()))); })