#![deny(missing_docs)]
use std;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt;
use std::hash::Hash;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use semver;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use crate::error::*;
use crate::target::TargetSpec;
use crate::util::*;
#[derive(Debug)]
pub struct Manifest {
pub package: Package,
pub dependencies: HashMap<String, Dependency>,
pub sources: Option<Sources>,
pub export_include_dirs: Vec<PathBuf>,
pub plugins: HashMap<String, PathBuf>,
pub frozen: bool,
pub workspace: Workspace,
pub vendor_package: Vec<VendorPackage>,
}
impl PrefixPaths for Manifest {
fn prefix_paths(self, prefix: &Path) -> Self {
Manifest {
package: self.package,
dependencies: self.dependencies.prefix_paths(prefix),
sources: self.sources.map(|src| src.prefix_paths(prefix)),
export_include_dirs: self
.export_include_dirs
.into_iter()
.map(|src| src.prefix_paths(prefix))
.collect(),
plugins: self.plugins.prefix_paths(prefix),
frozen: self.frozen,
workspace: self.workspace.prefix_paths(prefix),
vendor_package: self.vendor_package.prefix_paths(prefix),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Package {
pub name: String,
pub authors: Option<Vec<String>>,
}
#[derive(Debug)]
pub enum Dependency {
Version(semver::VersionReq),
Path(PathBuf),
GitRevision(String, String),
GitVersion(String, semver::VersionReq),
}
impl PrefixPaths for Dependency {
fn prefix_paths(self, prefix: &Path) -> Self {
match self {
Dependency::Path(p) => Dependency::Path(p.prefix_paths(prefix)),
v => v,
}
}
}
impl Serialize for Dependency {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeMap;
match *self {
Dependency::Version(ref version) => format!("{}", version).serialize(serializer),
Dependency::Path(ref path) => path.serialize(serializer),
Dependency::GitRevision(ref url, ref rev) => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("git", url)?;
map.serialize_entry("rev", rev)?;
map.end()
}
Dependency::GitVersion(ref url, ref version) => {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("git", url)?;
map.serialize_entry("version", &format!("{}", version))?;
map.end()
}
}
}
}
#[derive(Debug)]
pub struct Sources {
pub target: TargetSpec,
pub include_dirs: Vec<PathBuf>,
pub defines: HashMap<String, Option<String>>,
pub files: Vec<SourceFile>,
}
impl PrefixPaths for Sources {
fn prefix_paths(self, prefix: &Path) -> Self {
Sources {
target: self.target,
include_dirs: self.include_dirs.prefix_paths(prefix),
defines: self.defines,
files: self.files.prefix_paths(prefix),
}
}
}
pub enum SourceFile {
File(PathBuf),
Group(Box<Sources>),
}
impl fmt::Debug for SourceFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
SourceFile::File(ref path) => fmt::Debug::fmt(path, f),
SourceFile::Group(ref srcs) => fmt::Debug::fmt(srcs, f),
}
}
}
impl PrefixPaths for SourceFile {
fn prefix_paths(self, prefix: &Path) -> Self {
match self {
SourceFile::File(path) => SourceFile::File(path.prefix_paths(prefix)),
SourceFile::Group(group) => SourceFile::Group(Box::new(group.prefix_paths(prefix))),
}
}
}
#[derive(Debug, Default)]
pub struct Workspace {
pub checkout_dir: Option<PathBuf>,
pub package_links: HashMap<PathBuf, String>,
}
impl PrefixPaths for Workspace {
fn prefix_paths(self, prefix: &Path) -> Self {
Workspace {
checkout_dir: self.checkout_dir.prefix_paths(prefix),
package_links: self
.package_links
.into_iter()
.map(|(k, v)| (k.prefix_paths(prefix), v))
.collect(),
}
}
}
pub trait Validate {
type Output;
type Error;
fn validate(self) -> std::result::Result<Self::Output, Self::Error>;
}
impl<K, V> Validate for HashMap<K, V>
where
K: Hash + Eq,
V: Validate<Error = Error>,
{
type Output = HashMap<K, V::Output>;
type Error = (K, Error);
fn validate(self) -> std::result::Result<Self::Output, Self::Error> {
self.into_iter()
.map(|(k, v)| match v.validate() {
Ok(v) => Ok((k, v)),
Err(e) => Err((k, e)),
})
.collect()
}
}
impl<V> Validate for Vec<V>
where
V: Validate<Error = Error>,
{
type Output = Vec<V::Output>;
type Error = Error;
fn validate(self) -> std::result::Result<Self::Output, Self::Error> {
self.into_iter()
.map(|v| match v.validate() {
Ok(v) => Ok(v),
Err(e) => Err(e),
})
.collect()
}
}
impl<T> Validate for StringOrStruct<T>
where
T: Validate,
{
type Output = T::Output;
type Error = T::Error;
fn validate(self) -> std::result::Result<T::Output, T::Error> {
self.0.validate()
}
}
impl<T, F> Validate for SeqOrStruct<T, F>
where
T: Validate,
{
type Output = T::Output;
type Error = T::Error;
fn validate(self) -> std::result::Result<T::Output, T::Error> {
self.0.validate()
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialManifest {
pub package: Option<Package>,
pub dependencies: Option<HashMap<String, StringOrStruct<PartialDependency>>>,
pub sources: Option<SeqOrStruct<PartialSources, PartialSourceFile>>,
pub export_include_dirs: Option<Vec<PathBuf>>,
pub plugins: Option<HashMap<String, PathBuf>>,
pub frozen: Option<bool>,
pub workspace: Option<PartialWorkspace>,
pub vendor_package: Option<Vec<PartialVendorPackage>>,
}
impl Validate for PartialManifest {
type Output = Manifest;
type Error = Error;
fn validate(self) -> Result<Manifest> {
let pkg = match self.package {
Some(mut p) => {
p.name = p.name.to_lowercase();
p
}
None => return Err(Error::new("Missing package information.")),
};
let deps = match self.dependencies {
Some(d) => d
.into_iter()
.map(|(k, v)| (k.to_lowercase(), v))
.collect::<HashMap<_, _>>()
.validate()
.map_err(|(key, cause)| {
Error::chain(
format!("In dependency `{}` of package `{}`:", key, pkg.name),
cause,
)
})?,
None => HashMap::new(),
};
let srcs = match self.sources {
Some(s) => Some(s.validate().map_err(|cause| {
Error::chain(format!("In source list of package `{}`:", pkg.name), cause)
})?),
None => None,
};
let exp_inc_dirs = self.export_include_dirs.unwrap_or_default();
let plugins = match self.plugins {
Some(s) => s,
None => HashMap::new(),
};
let frozen = self.frozen.unwrap_or(false);
let workspace = match self.workspace {
Some(w) => w
.validate()
.map_err(|cause| Error::chain("In workspace configuration:", cause))?,
None => Workspace::default(),
};
let vendor_package = match self.vendor_package {
Some(vend) => vend
.validate()
.map_err(|cause| Error::chain("Unable to parse vendor_package", cause))?,
None => Vec::new(),
};
Ok(Manifest {
package: pkg,
dependencies: deps,
sources: srcs,
export_include_dirs: exp_inc_dirs,
plugins,
frozen,
workspace,
vendor_package,
})
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialDependency {
path: Option<PathBuf>,
git: Option<String>,
rev: Option<String>,
version: Option<String>,
}
impl FromStr for PartialDependency {
type Err = Void;
fn from_str(s: &str) -> std::result::Result<Self, Void> {
Ok(PartialDependency {
path: None,
git: None,
rev: None,
version: Some(s.into()),
})
}
}
impl PrefixPaths for PartialDependency {
fn prefix_paths(self, prefix: &Path) -> Self {
PartialDependency {
path: self.path.prefix_paths(prefix),
..self
}
}
}
impl Validate for PartialDependency {
type Output = Dependency;
type Error = Error;
fn validate(self) -> Result<Dependency> {
let version = match self.version {
Some(v) => Some(semver::VersionReq::parse(&v).map_err(|cause| {
Error::chain(
format!("\"{}\" is not a valid semantic version requirement.", v),
cause,
)
})?),
None => None,
};
if self.rev.is_some() && version.is_some() {
return Err(Error::new(
"A dependency cannot specify `version` and `rev` at the same time.",
));
}
if let Some(path) = self.path {
if let Some(list) = string_list(
self.git
.map(|_| "`git`")
.iter()
.chain(self.rev.map(|_| "`rev`").iter())
.chain(version.map(|_| "`version`").iter()),
",",
"or",
) {
Err(Error::new(format!(
"A `path` dependency cannot have a {} field.",
list
)))
} else {
Ok(Dependency::Path(path))
}
} else if let Some(git) = self.git {
if let Some(rev) = self.rev {
Ok(Dependency::GitRevision(git, rev))
} else if let Some(version) = version {
Ok(Dependency::GitVersion(git, version))
} else {
Err(Error::new(
"A `git` dependency must have either a `rev` or `version` field.",
))
}
} else if let Some(version) = version {
Ok(Dependency::Version(version))
} else {
Err(Error::new(
"A dependency must specify `version`, `path`, or `git`.",
))
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialSources {
pub target: Option<TargetSpec>,
pub include_dirs: Option<Vec<PathBuf>>,
pub defines: Option<HashMap<String, Option<String>>>,
pub files: Vec<PartialSourceFile>,
}
impl From<Vec<PartialSourceFile>> for PartialSources {
fn from(v: Vec<PartialSourceFile>) -> Self {
PartialSources {
target: None,
include_dirs: None,
defines: None,
files: v,
}
}
}
impl Validate for PartialSources {
type Output = Sources;
type Error = Error;
fn validate(self) -> Result<Sources> {
let include_dirs = self.include_dirs.unwrap_or_default();
let defines = self.defines.unwrap_or_default();
let files: Result<Vec<_>> = self.files.into_iter().map(|f| f.validate()).collect();
Ok(Sources {
target: self.target.unwrap_or(TargetSpec::Wildcard),
include_dirs,
defines,
files: files?,
})
}
}
#[derive(Debug)]
pub enum PartialSourceFile {
File(PathBuf),
Group(Box<PartialSources>),
}
impl Serialize for PartialSourceFile {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
PartialSourceFile::File(ref path) => path.serialize(serializer),
PartialSourceFile::Group(ref srcs) => srcs.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for PartialSourceFile {
fn deserialize<D>(deserializer: D) -> std::result::Result<PartialSourceFile, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
use std::result::Result;
struct Visitor;
impl<'de> de::Visitor<'de> for Visitor {
type Value = PartialSourceFile;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or map")
}
fn visit_str<E>(self, value: &str) -> Result<PartialSourceFile, E>
where
E: de::Error,
{
Ok(PartialSourceFile::File(value.into()))
}
fn visit_map<M>(self, visitor: M) -> Result<PartialSourceFile, M::Error>
where
M: de::MapAccess<'de>,
{
let srcs =
PartialSources::deserialize(de::value::MapAccessDeserializer::new(visitor))?;
Ok(PartialSourceFile::Group(Box::new(srcs)))
}
}
deserializer.deserialize_any(Visitor)
}
}
impl Validate for PartialSourceFile {
type Output = SourceFile;
type Error = Error;
fn validate(self) -> Result<SourceFile> {
match self {
PartialSourceFile::File(path) => Ok(SourceFile::File(path)),
PartialSourceFile::Group(srcs) => Ok(SourceFile::Group(Box::new(srcs.validate()?))),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialWorkspace {
pub checkout_dir: Option<PathBuf>,
pub package_links: Option<HashMap<PathBuf, String>>,
}
impl Validate for PartialWorkspace {
type Output = Workspace;
type Error = Error;
fn validate(self) -> Result<Workspace> {
Ok(Workspace {
checkout_dir: self.checkout_dir,
package_links: self.package_links.unwrap_or_default(),
})
}
}
pub trait Merge {
fn merge(self, other: Self) -> Self;
}
pub trait PrefixPaths {
fn prefix_paths(self, prefix: &Path) -> Self;
}
impl PrefixPaths for PathBuf {
fn prefix_paths(self, prefix: &Path) -> Self {
prefix.join(self)
}
}
impl<T> PrefixPaths for Option<T>
where
T: PrefixPaths,
{
fn prefix_paths(self, prefix: &Path) -> Self {
self.map(|inner| inner.prefix_paths(prefix))
}
}
impl<K, V> PrefixPaths for HashMap<K, V>
where
K: Hash + Eq,
V: PrefixPaths,
{
fn prefix_paths(self, prefix: &Path) -> Self {
self.into_iter()
.map(|(k, v)| (k, v.prefix_paths(prefix)))
.collect()
}
}
impl<V> PrefixPaths for Vec<V>
where
V: PrefixPaths,
{
fn prefix_paths(self, prefix: &Path) -> Self {
self.into_iter().map(|v| v.prefix_paths(prefix)).collect()
}
}
#[derive(Serialize, Debug)]
pub struct Config {
pub database: PathBuf,
pub git: String,
pub overrides: HashMap<String, Dependency>,
pub plugins: HashMap<String, Dependency>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialConfig {
pub database: Option<PathBuf>,
pub git: Option<String>,
pub overrides: Option<HashMap<String, PartialDependency>>,
pub plugins: Option<HashMap<String, PartialDependency>>,
}
impl PartialConfig {
pub fn new() -> PartialConfig {
PartialConfig {
database: None,
git: None,
overrides: None,
plugins: None,
}
}
}
impl Default for PartialConfig {
fn default() -> Self {
Self::new()
}
}
impl PrefixPaths for PartialConfig {
fn prefix_paths(self, prefix: &Path) -> Self {
PartialConfig {
database: self.database.prefix_paths(prefix),
overrides: self.overrides.prefix_paths(prefix),
plugins: self.plugins.prefix_paths(prefix),
..self
}
}
}
impl Merge for PartialConfig {
fn merge(self, other: PartialConfig) -> PartialConfig {
PartialConfig {
database: self.database.or(other.database),
git: self.git.or(other.git),
overrides: match (self.overrides, other.overrides) {
(Some(o), None) | (None, Some(o)) => Some(o),
(Some(mut o1), Some(o2)) => {
o1.extend(o2);
Some(o1)
}
(None, None) => None,
},
plugins: match (self.plugins, other.plugins) {
(Some(o), None) | (None, Some(o)) => Some(o),
(Some(mut o1), Some(o2)) => {
o1.extend(o2);
Some(o1)
}
(None, None) => None,
},
}
}
}
impl Validate for PartialConfig {
type Output = Config;
type Error = Error;
fn validate(self) -> Result<Config> {
Ok(Config {
database: match self.database {
Some(db) => db,
None => return Err(Error::new("Database directory not configured")),
},
git: match self.git {
Some(git) => git,
None => return Err(Error::new("Git command or path to binary not configured")),
},
overrides: match self.overrides {
Some(d) => d.validate().map_err(|(key, cause)| {
Error::chain(format!("In override `{}`:", key), cause)
})?,
None => HashMap::new(),
},
plugins: match self.plugins {
Some(d) => d
.validate()
.map_err(|(key, cause)| Error::chain(format!("In plugin `{}`:", key), cause))?,
None => HashMap::new(),
},
})
}
}
#[derive(Serialize, Debug)]
pub struct VendorPackage {
pub name: String,
pub target_dir: PathBuf,
pub upstream: Dependency,
pub mapping: Vec<FromToLink>,
pub patch_dir: Option<PathBuf>,
pub include_from_upstream: Vec<String>,
pub exclude_from_upstream: Vec<String>,
}
impl PrefixPaths for VendorPackage {
fn prefix_paths(self, prefix: &Path) -> Self {
let patch_root = self.patch_dir.prefix_paths(prefix);
VendorPackage {
name: self.name,
target_dir: self.target_dir.prefix_paths(prefix),
upstream: self.upstream,
mapping: self
.mapping
.into_iter()
.map(|ftl| FromToLink {
from: ftl.from,
to: ftl.to,
patch_dir: ftl.patch_dir.map(|dir| {
dir.prefix_paths(&patch_root.clone().expect(
"A mapping has a local patch_dir, but no global patch_dir is defined.",
))
}),
})
.collect(),
patch_dir: patch_root,
include_from_upstream: self.include_from_upstream,
exclude_from_upstream: self.exclude_from_upstream,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PartialVendorPackage {
pub name: Option<String>,
pub target_dir: Option<PathBuf>,
pub upstream: Option<PartialDependency>,
pub mapping: Option<Vec<FromToLink>>,
pub patch_dir: Option<PathBuf>,
pub include_from_upstream: Option<Vec<String>>,
pub exclude_from_upstream: Option<Vec<String>>,
}
impl Validate for PartialVendorPackage {
type Output = VendorPackage;
type Error = Error;
fn validate(self) -> Result<VendorPackage> {
Ok(VendorPackage {
name: match self.name {
Some(name) => name,
None => return Err(Error::new("external import name missing")),
},
target_dir: match self.target_dir {
Some(target_dir) => target_dir,
None => return Err(Error::new("external import target dir missing")),
},
upstream: match self.upstream {
Some(upstream) => upstream.validate().map_err(|cause| {
Error::chain("Unable to parse external import upstream", cause)
})?,
None => return Err(Error::new("external import upstream missing")),
},
mapping: match self.mapping {
Some(mapping) => mapping,
None => Vec::new(),
},
patch_dir: self.patch_dir,
include_from_upstream: match self.include_from_upstream {
Some(include_from_upstream) => include_from_upstream,
None => vec![String::from("**")],
},
exclude_from_upstream: {
let mut excl = match self.exclude_from_upstream {
Some(exclude_from_upstream) => exclude_from_upstream,
None => Vec::new(),
};
excl.push(String::from(".git"));
excl
},
})
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct FromToLink {
pub from: PathBuf,
pub to: PathBuf,
pub patch_dir: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Locked {
pub packages: BTreeMap<String, LockedPackage>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct LockedPackage {
pub revision: Option<String>,
pub version: Option<String>,
#[serde(with = "serde_yaml::with::singleton_map")]
pub source: LockedSource,
pub dependencies: BTreeSet<String>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum LockedSource {
Path(PathBuf),
Git(String),
Registry(String),
}