Skip to content

Commit e420ccf

Browse files
bors[bot]tofay
andauthored
Merge #336
336: alternative registry support r=ordian a=tofay This PR adds support for alternative registries. The main change is updating the `fetch::get_latest_dependency` to optionally accept a registry URL. I'm using URLs not registry names, because `cargo metadata` returns the URL of the index whenever the dependency is being sourced from an alternative registry. When adding dependencies from alternative registries, the user specifies a registry name which is then converted to the relevant URL. For this purpose, the cargo config parsing code in `registry.rs` is updated to handle registries other than crates-io. W.r.t testing, I've only added tests for the CLI/toml-editing aspects. I've been successfully using `cargo upgrade` with my company's alternative registry, but I've not added tests that actually query an alternative registry, because the tests currently rely on the user's cargo config. Is that acceptable? (Cargo itself has private test fixtures for working with alternative registries - this could be reused when/if cargo-edit is moved into cargo.) Fixes #201 Fixes #339 Co-authored-by: Tom Fay <[email protected]>
2 parents ca3462e + aaeba3c commit e420ccf

14 files changed

+839
-480
lines changed

Cargo.lock

+407-403
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bin/add/args.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Handle `cargo add` arguments
22
3-
use cargo_edit::{find, Dependency};
3+
use cargo_edit::{find, registry_url, Dependency};
44
use cargo_edit::{get_latest_dependency, CrateName};
55
use semver;
66
use std::path::PathBuf;
@@ -104,6 +104,10 @@ pub struct Args {
104104
/// Keep dependencies sorted
105105
#[structopt(long = "sort", short = "s")]
106106
pub sort: bool,
107+
108+
/// Registry to use
109+
#[structopt(long = "registry", conflicts_with = "git", conflicts_with = "path")]
110+
pub registry: Option<String>,
107111
}
108112

109113
fn parse_version_req(s: &str) -> Result<&str> {
@@ -153,6 +157,8 @@ impl Args {
153157
} else {
154158
assert_eq!(self.git.is_some() && self.vers.is_some(), false);
155159
assert_eq!(self.git.is_some() && self.path.is_some(), false);
160+
assert_eq!(self.git.is_some() && self.registry.is_some(), false);
161+
assert_eq!(self.path.is_some() && self.registry.is_some(), false);
156162

157163
let mut dependency = Dependency::new(crate_name.name());
158164

@@ -165,12 +171,18 @@ impl Args {
165171
if let Some(version) = &self.vers {
166172
dependency = dependency.set_version(parse_version_req(version)?);
167173
}
174+
let registry_url = if let Some(registry) = &self.registry {
175+
Some(registry_url(&find(&self.manifest_path)?, Some(registry))?)
176+
} else {
177+
None
178+
};
168179

169180
if self.git.is_none() && self.path.is_none() && self.vers.is_none() {
170181
let dep = get_latest_dependency(
171182
crate_name.name(),
172183
self.allow_prerelease,
173184
&find(&self.manifest_path)?,
185+
&registry_url,
174186
)?;
175187
let v = format!(
176188
"{prefix}{version}",
@@ -182,6 +194,12 @@ impl Args {
182194
dependency = dep.set_version(&v);
183195
}
184196

197+
// Set the registry after getting the latest version as
198+
// get_latest_dependency returns a registry-less Dependency
199+
if let Some(registry) = &self.registry {
200+
dependency = dependency.set_registry(registry);
201+
}
202+
185203
Ok(dependency)
186204
}
187205
}
@@ -236,6 +254,7 @@ impl Default for Args {
236254
quiet: false,
237255
offline: true,
238256
sort: false,
257+
registry: None,
239258
}
240259
}
241260
}

src/bin/add/main.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
extern crate error_chain;
1616

1717
use crate::args::{Args, Command};
18-
use cargo_edit::{find, update_registry_index, Dependency, Manifest};
18+
use cargo_edit::{find, registry_url, update_registry_index, Dependency, Manifest};
1919
use std::io::Write;
2020
use std::process;
2121
use structopt::StructOpt;
22-
use toml_edit::Item as TomlItem;
2322
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
23+
use toml_edit::Item as TomlItem;
2424

2525
mod args;
2626

@@ -83,7 +83,11 @@ fn handle_add(args: &Args) -> Result<()> {
8383
let deps = &args.parse_dependencies()?;
8484

8585
if !args.offline {
86-
update_registry_index(&find(manifest_path)?)?;
86+
let url = registry_url(
87+
&find(&manifest_path)?,
88+
args.registry.as_ref().map(String::as_ref),
89+
)?;
90+
update_registry_index(&url)?;
8791
}
8892

8993
deps.iter()

src/bin/upgrade/main.rs

+68-31
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ extern crate error_chain;
1616

1717
use crate::errors::*;
1818
use cargo_edit::{
19-
find, get_latest_dependency, update_registry_index, CrateName, Dependency, LocalManifest,
19+
find, get_latest_dependency, registry_url, update_registry_index, CrateName, Dependency,
20+
LocalManifest,
2021
};
2122
use failure::Fail;
22-
use std::collections::HashMap;
23+
use std::collections::{HashMap, HashSet};
2324
use std::io::Write;
2425
use std::path::{Path, PathBuf};
2526
use std::process;
2627
use structopt::StructOpt;
2728
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
29+
use url::Url;
2830

2931
mod errors {
3032
error_chain! {
@@ -153,37 +155,49 @@ impl Manifests {
153155
}
154156
}
155157

156-
Ok(DesiredUpgrades(if only_update.is_empty() {
157-
// User hasn't asked for any specific dependencies to be upgraded, so upgrade all the
158-
// dependencies.
158+
// Map the names of user-specified dependencies to the (optionally) requested version.
159+
let selected_dependencies = only_update
160+
.into_iter()
161+
.map(|name| {
162+
if let Some(dependency) = CrateName::new(&name.clone()).parse_as_version()? {
163+
Ok((
164+
dependency.name.clone(),
165+
dependency.version().map(String::from),
166+
))
167+
} else {
168+
Ok((name, None))
169+
}
170+
})
171+
.collect::<Result<HashMap<_, _>>>()?;
172+
173+
Ok(DesiredUpgrades(
159174
self.0
160175
.iter()
161176
.flat_map(|&(_, ref package)| package.dependencies.clone())
162177
.filter(is_version_dep)
163-
.map(|dependency| {
164-
let mut dep = Dependency::new(&dependency.name);
165-
if let Some(rename) = dependency.rename {
166-
dep = dep.set_rename(&rename);
167-
}
168-
(dep, None)
169-
})
170-
.collect()
171-
} else {
172-
only_update
173-
.into_iter()
174-
.map(|name| {
175-
if let Some(dependency) = CrateName::new(&name.clone()).parse_as_version()? {
176-
Ok((
177-
dependency.name.clone(),
178-
dependency.version().map(String::from),
179-
))
178+
.filter_map(|dependency| {
179+
if selected_dependencies.is_empty() {
180+
// User hasn't asked for any specific dependencies to be upgraded,
181+
// so upgrade all the dependencies.
182+
let mut dep = Dependency::new(&dependency.name);
183+
if let Some(rename) = dependency.rename {
184+
dep = dep.set_rename(&rename);
185+
}
186+
Some((dep, (dependency.registry, None)))
180187
} else {
181-
Ok((name, None))
188+
// User has asked for specific dependencies. Check if this dependency
189+
// was specified, populating the registry from the lockfile metadata.
190+
match selected_dependencies.get(&dependency.name) {
191+
Some(version) => Some((
192+
Dependency::new(&dependency.name),
193+
(dependency.registry, version.clone()),
194+
)),
195+
None => None,
196+
}
182197
}
183-
.map(move |(name, version)| (Dependency::new(&name), version))
184198
})
185-
.collect::<Result<_>>()?
186-
}))
199+
.collect(),
200+
))
187201
}
188202

189203
/// Upgrade the manifests on disk following the previously-determined upgrade schema.
@@ -222,8 +236,9 @@ impl Manifests {
222236
}
223237
}
224238

225-
/// The set of dependencies to be upgraded, alongside desired versions, if specified by the user.
226-
struct DesiredUpgrades(HashMap<Dependency, Option<String>>);
239+
/// The set of dependencies to be upgraded, alongside the registries returned from cargo metadata, and
240+
/// the desired versions, if specified by the user.
241+
struct DesiredUpgrades(HashMap<Dependency, (Option<String>, Option<String>)>);
227242

228243
/// The complete specification of the upgrades that will be performed. Map of the dependency names
229244
/// to the new versions.
@@ -235,11 +250,17 @@ impl DesiredUpgrades {
235250
fn get_upgraded(self, allow_prerelease: bool, manifest_path: &Path) -> Result<ActualUpgrades> {
236251
self.0
237252
.into_iter()
238-
.map(|(dep, version)| {
253+
.map(|(dep, (registry, version))| {
239254
if let Some(v) = version {
240255
Ok((dep, v))
241256
} else {
242-
get_latest_dependency(&dep.name, allow_prerelease, manifest_path)
257+
let registry_url = match registry {
258+
Some(x) => Some(Url::parse(&x).map_err(|_| {
259+
ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
260+
})?),
261+
None => None,
262+
};
263+
get_latest_dependency(&dep.name, allow_prerelease, manifest_path, &registry_url)
243264
.map(|new_dep| {
244265
(
245266
dep,
@@ -270,7 +291,8 @@ fn process(args: Args) -> Result<()> {
270291
} = args;
271292

272293
if !args.offline {
273-
update_registry_index(&find(&manifest_path)?)?;
294+
let url = registry_url(&find(&manifest_path)?, None)?;
295+
update_registry_index(&url)?;
274296
}
275297

276298
let manifests = if all {
@@ -281,6 +303,21 @@ fn process(args: Args) -> Result<()> {
281303

282304
let existing_dependencies = manifests.get_dependencies(dependency)?;
283305

306+
// Update indices for any alternative registries, unless
307+
// we're offline.
308+
if !args.offline {
309+
for registry_url in existing_dependencies
310+
.0
311+
.values()
312+
.filter_map(|(registry, _)| registry.as_ref())
313+
.collect::<HashSet<_>>()
314+
{
315+
update_registry_index(&Url::parse(registry_url).map_err(|_| {
316+
ErrorKind::CargoEditLib(::cargo_edit::ErrorKind::InvalidCargoConfig)
317+
})?)?;
318+
}
319+
}
320+
284321
let upgraded_dependencies =
285322
existing_dependencies.get_upgraded(allow_prerelease, &find(&manifest_path)?)?;
286323

src/dependency.rs

+42-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ enum DependencySource {
55
Version {
66
version: Option<String>,
77
path: Option<String>,
8+
registry: Option<String>,
89
},
910
Git(String),
1011
}
@@ -32,6 +33,7 @@ impl Default for Dependency {
3233
source: DependencySource::Version {
3334
version: None,
3435
path: None,
36+
registry: None,
3537
},
3638
}
3739
}
@@ -52,14 +54,14 @@ impl Dependency {
5254
// store in the cargo toml files. This would cause a warning upon compilation
5355
// ("version requirement […] includes semver metadata which will be ignored")
5456
let version = version.split('+').next().unwrap();
55-
let old_source = self.source;
56-
let old_path = match old_source {
57-
DependencySource::Version { path, .. } => path,
58-
_ => None,
57+
let (old_path, old_registry) = match self.source {
58+
DependencySource::Version { path, registry, .. } => (path, registry),
59+
_ => (None, None),
5960
};
6061
self.source = DependencySource::Version {
6162
version: Some(version.into()),
6263
path: old_path,
64+
registry: old_registry,
6365
};
6466
self
6567
}
@@ -72,14 +74,14 @@ impl Dependency {
7274

7375
/// Set dependency to a given path
7476
pub fn set_path(mut self, path: &str) -> Dependency {
75-
let old_source = self.source;
76-
let old_version = match old_source {
77+
let old_version = match self.source {
7778
DependencySource::Version { version, .. } => version,
7879
_ => None,
7980
};
8081
self.source = DependencySource::Version {
8182
version: old_version,
8283
path: Some(path.into()),
84+
registry: None,
8385
};
8486
self
8587
}
@@ -109,6 +111,20 @@ impl Dependency {
109111
&self.rename().unwrap_or(&self.name)
110112
}
111113

114+
/// Set the value of registry for the dependency
115+
pub fn set_registry(mut self, registry: &str) -> Dependency {
116+
let old_version = match self.source {
117+
DependencySource::Version { version, .. } => version,
118+
_ => None,
119+
};
120+
self.source = DependencySource::Version {
121+
version: old_version,
122+
path: None,
123+
registry: Some(registry.into()),
124+
};
125+
self
126+
}
127+
112128
/// Get version of dependency
113129
pub fn version(&self) -> Option<&str> {
114130
if let DependencySource::Version {
@@ -150,6 +166,7 @@ impl Dependency {
150166
DependencySource::Version {
151167
version: Some(v),
152168
path: None,
169+
registry: None,
153170
},
154171
None,
155172
) => toml_edit::value(v),
@@ -158,13 +175,20 @@ impl Dependency {
158175
let mut data = toml_edit::InlineTable::default();
159176

160177
match source {
161-
DependencySource::Version { version, path } => {
178+
DependencySource::Version {
179+
version,
180+
path,
181+
registry,
182+
} => {
162183
if let Some(v) = version {
163184
data.get_or_insert("version", v);
164185
}
165186
if let Some(p) = path {
166187
data.get_or_insert("path", p);
167188
}
189+
if let Some(r) = registry {
190+
data.get_or_insert("registry", r);
191+
}
168192
}
169193
DependencySource::Git(v) => {
170194
data.get_or_insert("git", v);
@@ -268,6 +292,17 @@ mod tests {
268292
assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
269293
}
270294

295+
#[test]
296+
fn to_toml_dep_from_alt_registry() {
297+
let toml = Dependency::new("dep").set_registry("alternative").to_toml();
298+
299+
assert_eq!(toml.0, "dep".to_owned());
300+
assert!(toml.1.is_inline_table());
301+
302+
let dep = toml.1.as_inline_table().unwrap();
303+
assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
304+
}
305+
271306
#[test]
272307
fn to_toml_complex_dep() {
273308
let toml = Dependency::new("dep")

src/errors.rs

+4
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,9 @@ error_chain! {
6565
description("Unable to find the source specified by 'replace-with'")
6666
display("The source '{}' could not be found", name)
6767
}
68+
/// Unable to find the specified registry
69+
NoSuchRegistryFound(name: String) {
70+
display("The registry '{}' could not be found", name)
71+
}
6872
}
6973
}

0 commit comments

Comments
 (0)