mirror of https://github.com/rust-lang/cargo
260 lines
9.1 KiB
Rust
260 lines
9.1 KiB
Rust
use std::collections::{BTreeMap, BTreeSet};
|
|
use std::env;
|
|
use std::io::prelude::*;
|
|
use std::io::SeekFrom;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use semver::VersionReq;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::core::package::PackageSet;
|
|
use crate::core::source::SourceMap;
|
|
use crate::core::PackageId;
|
|
use crate::core::{Dependency, Package, Source, SourceId};
|
|
use crate::sources::PathSource;
|
|
use crate::util::errors::{CargoResult, CargoResultExt};
|
|
use crate::util::{internal, Config, ToSemver};
|
|
use crate::util::{FileLock, Filesystem};
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
#[serde(untagged)]
|
|
pub enum CrateListing {
|
|
V1(CrateListingV1),
|
|
Empty(Empty),
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Empty {}
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
pub struct CrateListingV1 {
|
|
v1: BTreeMap<PackageId, BTreeSet<String>>,
|
|
}
|
|
|
|
impl CrateListingV1 {
|
|
pub fn v1(&self) -> &BTreeMap<PackageId, BTreeSet<String>> {
|
|
&self.v1
|
|
}
|
|
|
|
pub fn v1_mut(&mut self) -> &mut BTreeMap<PackageId, BTreeSet<String>> {
|
|
&mut self.v1
|
|
}
|
|
}
|
|
|
|
pub fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<Filesystem> {
|
|
let config_root = config.get_path("install.root")?;
|
|
Ok(flag
|
|
.map(PathBuf::from)
|
|
.or_else(|| env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
|
|
.or_else(move || config_root.map(|v| v.val))
|
|
.map(Filesystem::new)
|
|
.unwrap_or_else(|| config.home().clone()))
|
|
}
|
|
|
|
pub fn path_source<'a>(source_id: SourceId, config: &'a Config) -> CargoResult<PathSource<'a>> {
|
|
let path = source_id
|
|
.url()
|
|
.to_file_path()
|
|
.map_err(|()| failure::format_err!("path sources must have a valid path"))?;
|
|
Ok(PathSource::new(&path, source_id, config))
|
|
}
|
|
|
|
pub fn select_pkg<'a, T>(
|
|
mut source: T,
|
|
name: Option<&str>,
|
|
vers: Option<&str>,
|
|
config: &Config,
|
|
needs_update: bool,
|
|
list_all: &mut dyn FnMut(&mut T) -> CargoResult<Vec<Package>>,
|
|
) -> CargoResult<(Package, Box<dyn Source + 'a>)>
|
|
where
|
|
T: Source + 'a,
|
|
{
|
|
if needs_update {
|
|
source.update()?;
|
|
}
|
|
|
|
match name {
|
|
Some(name) => {
|
|
let vers = match vers {
|
|
Some(v) => {
|
|
// If the version begins with character <, >, =, ^, ~ parse it as a
|
|
// version range, otherwise parse it as a specific version
|
|
let first = v.chars().nth(0).ok_or_else(|| {
|
|
failure::format_err!("no version provided for the `--vers` flag")
|
|
})?;
|
|
|
|
match first {
|
|
'<' | '>' | '=' | '^' | '~' => match v.parse::<VersionReq>() {
|
|
Ok(v) => Some(v.to_string()),
|
|
Err(_) => failure::bail!(
|
|
"the `--vers` provided, `{}`, is \
|
|
not a valid semver version requirement\n\n
|
|
Please have a look at \
|
|
https://doc.crates.io/specifying-dependencies.html \
|
|
for the correct format",
|
|
v
|
|
),
|
|
},
|
|
_ => match v.to_semver() {
|
|
Ok(v) => Some(format!("={}", v)),
|
|
Err(_) => {
|
|
let mut msg = format!(
|
|
"\
|
|
the `--vers` provided, `{}`, is \
|
|
not a valid semver version\n\n\
|
|
historically Cargo treated this \
|
|
as a semver version requirement \
|
|
accidentally\nand will continue \
|
|
to do so, but this behavior \
|
|
will be removed eventually",
|
|
v
|
|
);
|
|
|
|
// If it is not a valid version but it is a valid version
|
|
// requirement, add a note to the warning
|
|
if v.parse::<VersionReq>().is_ok() {
|
|
msg.push_str(&format!(
|
|
"\nif you want to specify semver range, \
|
|
add an explicit qualifier, like ^{}",
|
|
v
|
|
));
|
|
}
|
|
config.shell().warn(&msg)?;
|
|
Some(v.to_string())
|
|
}
|
|
},
|
|
}
|
|
}
|
|
None => None,
|
|
};
|
|
let vers = vers.as_ref().map(|s| &**s);
|
|
let vers_spec = if vers.is_none() && source.source_id().is_registry() {
|
|
// Avoid pre-release versions from crate.io
|
|
// unless explicitly asked for
|
|
Some("*")
|
|
} else {
|
|
vers
|
|
};
|
|
let dep = Dependency::parse_no_deprecated(name, vers_spec, source.source_id())?;
|
|
let deps = source.query_vec(&dep)?;
|
|
let pkgid = match deps.iter().map(|p| p.package_id()).max() {
|
|
Some(pkgid) => pkgid,
|
|
None => {
|
|
let vers_info = vers
|
|
.map(|v| format!(" with version `{}`", v))
|
|
.unwrap_or_default();
|
|
failure::bail!(
|
|
"could not find `{}` in {}{}",
|
|
name,
|
|
source.source_id(),
|
|
vers_info
|
|
)
|
|
}
|
|
};
|
|
|
|
let pkg = {
|
|
let mut map = SourceMap::new();
|
|
map.insert(Box::new(&mut source));
|
|
PackageSet::new(&[pkgid], map, config)?
|
|
.get_one(pkgid)?
|
|
.clone()
|
|
};
|
|
Ok((pkg, Box::new(source)))
|
|
}
|
|
None => {
|
|
let candidates = list_all(&mut source)?;
|
|
let binaries = candidates
|
|
.iter()
|
|
.filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
|
|
let examples = candidates
|
|
.iter()
|
|
.filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
|
|
let pkg = match one(binaries, |v| multi_err("binaries", v))? {
|
|
Some(p) => p,
|
|
None => match one(examples, |v| multi_err("examples", v))? {
|
|
Some(p) => p,
|
|
None => failure::bail!(
|
|
"no packages found with binaries or \
|
|
examples"
|
|
),
|
|
},
|
|
};
|
|
return Ok((pkg.clone(), Box::new(source)));
|
|
|
|
fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
|
|
pkgs.sort_unstable_by_key(|a| a.name());
|
|
format!(
|
|
"multiple packages with {} found: {}",
|
|
kind,
|
|
pkgs.iter()
|
|
.map(|p| p.name().as_str())
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
|
|
where
|
|
I: Iterator,
|
|
F: FnOnce(Vec<I::Item>) -> String,
|
|
{
|
|
match (i.next(), i.next()) {
|
|
(Some(i1), Some(i2)) => {
|
|
let mut v = vec![i1, i2];
|
|
v.extend(i);
|
|
Err(failure::format_err!("{}", f(v)))
|
|
}
|
|
(Some(i), None) => Ok(Some(i)),
|
|
(None, _) => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub fn read_crate_list(file: &FileLock) -> CargoResult<CrateListingV1> {
|
|
let listing = (|| -> CargoResult<_> {
|
|
let mut contents = String::new();
|
|
file.file().read_to_string(&mut contents)?;
|
|
let listing =
|
|
toml::from_str(&contents).chain_err(|| internal("invalid TOML found for metadata"))?;
|
|
match listing {
|
|
CrateListing::V1(v1) => Ok(v1),
|
|
CrateListing::Empty(_) => Ok(CrateListingV1 {
|
|
v1: BTreeMap::new(),
|
|
}),
|
|
}
|
|
})()
|
|
.chain_err(|| {
|
|
failure::format_err!(
|
|
"failed to parse crate metadata at `{}`",
|
|
file.path().to_string_lossy()
|
|
)
|
|
})?;
|
|
Ok(listing)
|
|
}
|
|
|
|
pub fn write_crate_list(file: &FileLock, listing: CrateListingV1) -> CargoResult<()> {
|
|
(|| -> CargoResult<_> {
|
|
let mut file = file.file();
|
|
file.seek(SeekFrom::Start(0))?;
|
|
file.set_len(0)?;
|
|
let data = toml::to_string(&CrateListing::V1(listing))?;
|
|
file.write_all(data.as_bytes())?;
|
|
Ok(())
|
|
})()
|
|
.chain_err(|| {
|
|
failure::format_err!(
|
|
"failed to write crate metadata at `{}`",
|
|
file.path().to_string_lossy()
|
|
)
|
|
})?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn metadata(config: &Config, root: &Filesystem) -> CargoResult<FileLock> {
|
|
root.open_rw(Path::new(".crates.toml"), config, "crate metadata")
|
|
}
|