pub struct CliUnstable {
pub print_im_a_teapot: bool,
pub unstable_options: bool,
+ pub offline: bool,
}
impl CliUnstable {
match k {
"print-im-a-teapot" => self.print_im_a_teapot = parse_bool(v)?,
"unstable-options" => self.unstable_options = true,
+ "offline" => self.offline = true,
_ => bail!("unknown `-Z` flag specified: {}", k),
}
None => return Err(activation_error(&cx, registry, &parent,
&dep,
cx.prev_active(&dep),
- &candidates)),
+ &candidates, config)),
Some(candidate) => candidate,
}
}
parent: &Summary,
dep: &Dependency,
prev_active: &[Summary],
- candidates: &[Candidate]) -> CargoError {
+ candidates: &[Candidate],
+ config: Option<&Config>) -> CargoError {
if !candidates.is_empty() {
let mut msg = format!("failed to select a version for `{}` \
(required by `{}`):\n\
b.version().cmp(a.version())
});
- let msg = if !candidates.is_empty() {
+ let mut msg = if !candidates.is_empty() {
let versions = {
let mut versions = candidates.iter().take(3).map(|cand| {
cand.version().to_string()
dep.version_req())
};
+ if let Some(config) = config {
+ if config.cli_unstable().offline {
+ msg.push_str("\nperhaps an error occurred because you are using \
+ the offline mode");
+ }
+ }
+
format_err!("{}", msg)
}
bail!("you can't generate a lockfile for an empty workspace.")
}
+ if opts.config.cli_unstable().offline {
+ bail!("you can't update in the offline mode");
+ }
+
let previous_resolve = match ops::load_pkg_lockfile(ws)? {
Some(resolve) => resolve,
None => return generate_lockfile(ws),
}
if !ws.config().lock_update_allowed() {
+ if ws.config().cli_unstable().offline {
+ bail!("can't update in the offline mode");
+ }
+
let flag = if ws.config().network_allowed() {"--locked"} else {"--frozen"};
bail!("the lock file needs to be updated but {} was passed to \
prevent this", flag);
/// Create a new HTTP handle with appropriate global configuration for cargo.
pub fn http_handle(config: &Config) -> CargoResult<Easy> {
- if !config.network_allowed() {
+ if config.frozen() {
bail!("attempting to make an HTTP request, but --frozen was \
specified")
}
+ if !config.network_allowed() {
+ bail!("can't make HTTP request in the offline mode")
+ }
// The timeout option for libcurl by default times out the entire transfer,
// but we probably don't want this. Instead we only set timeouts for the
let db_path = lock.parent().join("db").join(&self.ident);
+ if self.config.cli_unstable().offline && !db_path.exists() {
+ bail!("can't checkout from '{}': you are in the offline mode", self.remote.url());
+ }
+
// Resolve our reference to an actual revision, and check if the
// database already has that revision. If it does, we just load a
// database pinned at that revision, and if we don't we issue an update
let should_update = actual_rev.is_err() ||
self.source_id.precise().is_none();
- let (db, actual_rev) = if should_update {
+ let (db, actual_rev) = if should_update && !self.config.cli_unstable().offline {
self.config.shell().status("Updating",
format!("git repository `{}`", self.remote.url()))?;
url: &Url,
refspec: &str,
config: &Config) -> CargoResult<()> {
- if !config.network_allowed() {
+ if config.frozen() {
bail!("attempting to update a git repository, but --frozen \
was specified")
}
+ if !config.network_allowed() {
+ bail!("can't update a git repository in the offline mode")
+ }
// If we're fetching from github, attempt github's special fast path for
// testing if we've already got an up-to-date copy of the repository
.map(|s| s.trim())
.filter(|l| !l.is_empty());
+ let online = !self.config.cli_unstable().offline;
// Attempt forwards-compatibility on the index by ignoring
// everything that we ourselves don't understand, that should
// allow future cargo implementations to break the
// interpretation of each line here and older cargo will simply
// ignore the new lines.
ret.extend(lines.filter_map(|line| {
- self.parse_registry_package(line).ok()
+ self.parse_registry_package(line).ok().and_then(|v|{
+ if online || load.is_crate_downloaded(v.0.package_id()) {
+ Some(v)
+ } else {
+ None
+ }
+ })
}));
Ok(())
fn download(&mut self,
pkg: &PackageId,
checksum: &str) -> CargoResult<FileLock>;
+
+ fn is_crate_downloaded(&self, _pkg: &PackageId) -> bool { true }
}
mod index;
}
fn update_index(&mut self) -> CargoResult<()> {
+ if self.config.cli_unstable().offline {
+ return Ok(());
+ }
+
// Ensure that we'll actually be able to acquire an HTTP handle later on
// once we start trying to download crates. This will weed out any
// problems with `.cargo/config` configuration related to HTTP.
dst.seek(SeekFrom::Start(0))?;
Ok(dst)
}
+
+
+ fn is_crate_downloaded(&self, pkg: &PackageId) -> bool {
+ let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
+ let path = Path::new(&filename);
+
+ if let Ok(dst) = self.cache_path.open_ro(path, self.config, &filename) {
+ if let Ok(meta) = dst.file().metadata(){
+ return meta.len() > 0;
+ }
+ }
+ false
+ }
+
}
impl<'cfg> Drop for RemoteRegistry<'cfg> {
}
pub fn network_allowed(&self) -> bool {
- !self.frozen
+ !self.frozen() && !self.cli_unstable().offline
+ }
+
+ pub fn frozen(&self) -> bool {
+ self.frozen
}
pub fn lock_update_allowed(&self) -> bool {
use cargotest::support::{ProjectBuilder};
use cargotest::support::{project, execs, main_file, basic_bin_manifest};
use cargotest::support::registry::Package;
+use cargotest::ChannelChanger;
use hamcrest::{assert_that, existing_file, existing_dir, is_not};
use tempdir::TempDir;
Did you mean `a`?"));
}
+#[test]
+fn cargo_compile_path_with_offline() {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(0));
+}
+
+#[test]
+fn cargo_compile_with_downloaded_dependency_with_offline() {
+ Package::new("present_dep", "1.2.3")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "present_dep"
+ version = "1.2.3"
+ "#)
+ .file("src/lib.rs", "")
+ .publish();
+
+ {
+ // make package downloaded
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2.3"
+ "#)
+ .file("src/lib.rs", "")
+ .build();
+ assert_that(p.cargo("build"),execs().with_status(0));
+ }
+
+ let p2 = project("bar")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2.3"
+ "#)
+ .file("src/lib.rs", "")
+ .build();
+
+ assert_that(p2.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(0)
+ .with_stderr_does_not_contain("Updating registry")
+ .with_stderr_does_not_contain("Downloading")
+ .with_stderr(format!("\
+[COMPILING] present_dep v1.2.3
+[COMPILING] bar v0.1.0 ({url})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ url = p2.url())));
+
+}
+
+#[test]
+fn cargo_compile_offline_not_try_update() {
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "bar"
+ version = "0.1.0"
+
+ [dependencies]
+ not_cached_dep = "1.2.5"
+ "#)
+ .file("src/lib.rs", "")
+ .build();
+
+ assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(101)
+ .with_stderr_does_not_contain("Updating registry")
+ .with_stderr_does_not_contain("Downloading")
+ .with_stderr("\
+error: no matching package named `not_cached_dep` found (required by `bar`)
+location searched: registry `[..]`
+version required: ^1.2.5
+perhaps an error occurred because you are using the offline mode"));
+}
+
+#[test]
+fn compile_offline_without_maxvers_cached(){
+ Package::new("present_dep", "1.2.1").publish();
+ Package::new("present_dep", "1.2.2").publish();
+
+ Package::new("present_dep", "1.2.3")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "present_dep"
+ version = "1.2.3"
+ "#)
+ .file("src/lib.rs", r#"pub fn get_version()->&'static str {"1.2.3"}"#)
+ .publish();
+
+ Package::new("present_dep", "1.2.5")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "present_dep"
+ version = "1.2.5"
+ "#)
+ .file("src/lib.rs", r#"pub fn get_version(){"1.2.5"}"#)
+ .publish();
+
+ {
+ // make package cached
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "=1.2.3"
+ "#)
+ .file("src/lib.rs", "")
+ .build();
+ assert_that(p.cargo("build"),execs().with_status(0));
+ }
+
+ let p2 = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ present_dep = "1.2"
+ "#)
+ .file("src/main.rs", "\
+extern crate present_dep;
+fn main(){
+ println!(\"{}\", present_dep::get_version());
+}")
+ .build();
+
+ assert_that(p2.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(0)
+ .with_stderr(format!("\
+[COMPILING] present_dep v1.2.3
+[COMPILING] foo v0.1.0 ({url})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ url = p2.url())));
+
+ assert_that(process(&p2.bin("foo")),
+ execs().with_status(0).with_stdout("1.2.3"));
+}
+
+#[test]
+fn compile_offline_while_transitive_dep_not_cached() {
+ let bar = Package::new("bar", "1.0.0");
+ let bar_path = bar.archive_dst();
+ bar.publish();
+
+ let mut content = Vec::new();
+
+ let mut file = File::open(bar_path.clone()).ok().unwrap();
+ let _ok = file.read_to_end(&mut content).ok().unwrap();
+ drop(file);
+ drop(File::create(bar_path.clone()).ok().unwrap() );
+
+ Package::new("foo", "0.1.0").dep("bar", "1.0.0").publish();
+
+ let p = project("transitive_load_test")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "transitive_load_test"
+ version = "0.0.1"
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/main.rs", "fn main(){}")
+ .build();
+
+ // simulate download foo, but fail to download bar
+ let _out = p.cargo("build").exec_with_output();
+
+ drop( File::create(bar_path).ok().unwrap().write_all(&content) );
+
+ assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(101)
+ .with_stderr_does_not_contain("Updating registry")
+ .with_stderr_does_not_contain("Downloading")
+ .with_stderr("\
+error: no matching package named `bar` found (required by `foo`)
+location searched: registry `[..]`
+version required: = 1.0.0
+perhaps an error occurred because you are using the offline mode"));
+}
+
#[test]
fn compile_path_dep_then_change_version() {
let p = project("foo")
use cargotest::sleep_ms;
use cargotest::support::paths::{self, CargoPathExt};
use cargotest::support::{git, project, execs, main_file, path2url};
+use cargotest::ChannelChanger;
use hamcrest::{assert_that,existing_file};
+use hamcrest::matchers::regex::matches_regex;
#[test]
fn cargo_compile_simple_git_dep() {
execs().with_stdout("hello world\n"));
}
+#[test]
+fn cargo_compile_forbird_git_httpsrepo_offline() {
+
+ let p = project("need_remote_repo")
+ .file("Cargo.toml", r#"
+
+ [project]
+ name = "need_remote_repo"
+ version = "0.5.0"
+ authors = ["chabapok@example.com"]
+
+ [dependencies.dep1]
+ git = 'https://github.com/some_user/dep1.git'
+ "#)
+ .file("src/main.rs", "")
+ .build();
+
+
+ assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(101).
+ with_stderr_does_not_contain("[UPDATING] git repository [..]").
+ with_stderr("\
+error: failed to load source for a dependency on `dep1`
+
+Caused by:
+ Unable to update https://github.com/some_user/dep1.git
+
+Caused by:
+ can't checkout from 'https://github.com/some_user/dep1.git': you are in the offline mode"));
+}
+
+
+#[test]
+fn cargo_compile_offline_with_cached_git_dep() {
+ let git_project = git::new("dep1", |project| {
+ project
+ .file("Cargo.toml", r#"
+ [project]
+ name = "dep1"
+ version = "0.5.0"
+ authors = ["chabapok@example.com"]
+
+ [lib]
+ name = "dep1""#)
+ .file("src/lib.rs", r#"
+ pub static COOL_STR:&str = "cached git repo rev1";
+ "#)
+ }).unwrap();
+
+ let repo = git2::Repository::open(&git_project.root()).unwrap();
+ let rev1 = repo.revparse_single("HEAD").unwrap().id();
+
+ // Commit the changes and make sure we trigger a recompile
+ File::create(&git_project.root().join("src/lib.rs")).unwrap().write_all(br#"
+ pub static COOL_STR:&str = "cached git repo rev2";
+ "#).unwrap();
+ git::add(&repo);
+ let rev2 = git::commit(&repo);
+
+ {
+ // cache to regisrty rev1 and rev2
+ let prj = project("cache_git_dep")
+ .file("Cargo.toml", &format!(r#"
+ [project]
+ name = "cache_git_dep"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ rev = "{}"
+ "#, git_project.url(), rev1.clone()))
+ .file("src/main.rs", "fn main(){}")
+ .build();
+ assert_that(prj.cargo("build"), execs().with_status(0));
+
+ File::create(&prj.root().join("Cargo.toml")).unwrap().write_all(
+ &format!(r#"
+ [project]
+ name = "cache_git_dep"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ rev = "{}"
+ "#, git_project.url(), rev2.clone()).as_bytes()
+ ).unwrap();
+ assert_that(prj.cargo("build"), execs().with_status(0));
+ }
+
+ let project = project("foo")
+ .file("Cargo.toml", &format!(r#"
+ [project]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ "#, git_project.url()))
+ .file("src/main.rs", &main_file(r#""hello from {}", dep1::COOL_STR"#, &["dep1"]))
+ .build();
+
+ let root = project.root();
+ let git_root = git_project.root();
+
+ assert_that(project.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_stderr(format!("\
+[COMPILING] dep1 v0.5.0 ({}#[..])
+[COMPILING] foo v0.5.0 ({})
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ path2url(git_root),
+ path2url(root)
+ )));
+
+ assert_that(&project.bin("foo"), existing_file());
+
+ assert_that(process(&project.bin("foo")),
+ execs().with_stdout("hello from cached git repo rev2\n"));
+
+ drop( File::create(&project.root().join("Cargo.toml")).unwrap()
+ .write_all(&format!(r#"
+ [project]
+ name = "foo"
+ version = "0.5.0"
+
+ [dependencies.dep1]
+ git = '{}'
+ rev = "{}"
+ "#, git_project.url(), rev1).as_bytes()).unwrap() );
+
+ let _out = project.cargo("build").masquerade_as_nightly_cargo()
+ .arg("-Zoffline").exec_with_output();
+ assert_that(process(&project.bin("foo")),
+ execs().with_stdout("hello from cached git repo rev1\n"));
+}
+
+
#[test]
fn cargo_compile_git_dep_branch() {
let project = project("foo");
"));
}
+#[test]
+fn update_offline(){
+ use cargotest::ChannelChanger;
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ bar = "*"
+ "#)
+ .file("src/main.rs", "fn main() {}")
+ .build();
+ assert_that(p.cargo("update").masquerade_as_nightly_cargo().arg("-Zoffline"),
+ execs().with_status(101).
+ with_stderr("error: you can't update in the offline mode[..]"));
+}
+
#[test]
fn dev_dependency_not_used() {
let p = project("foo")