#![doc(html_root_url = "https://docs.rs/rust_release_channel/0.3.0")]
#![warn(missing_docs)]
extern crate chrono;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate url;
extern crate url_serde;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
use std::collections;
use std::error;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Channel {
pub date: chrono::NaiveDate,
pub pkg: collections::BTreeMap<String, Package>,
pub renames: collections::BTreeMap<String, String>,
}
impl Channel {
pub fn new() -> Channel {
Channel {
date: chrono::Local::today().naive_local(),
pkg: collections::BTreeMap::new(),
renames: collections::BTreeMap::new(),
}
}
pub fn with_date(date: chrono::NaiveDate) -> Channel {
Channel {
date: date,
pkg: collections::BTreeMap::new(),
renames: collections::BTreeMap::new(),
}
}
pub fn to_string(&self) -> Result<String, toml::ser::Error> {
toml::to_string(self)
}
pub fn lookup_artefact<'s, 'q, T>(
&'s self,
query: T,
) -> Option<&'s Artefact>
where
T: Into<ArtefactQuery<'q>>,
{
let query: ArtefactQuery = query.into();
self.pkg
.get(query.pkg)
.or_else(|| {
self.renames.get(query.pkg).and_then(|new_name| {
self.pkg.get(new_name)
})
})
.and_then(|pkg| {
pkg.target.get(query.target)
})
}
pub fn validate<'a>(&'a self) -> Vec<ValidationError<'a>> {
let pkg_problems = self.pkg
.iter()
.flat_map(|(name, pkg)| pkg.validate(self, name));
let rename_problems = self.renames.iter().filter_map(|(old, new)| {
if old == new {
Some(ValidationError::RenameToItself(new))
} else if self.pkg.contains_key(old) {
Some(ValidationError::RenameExistingPackage(old))
} else if !self.pkg.contains_key(new) {
Some(ValidationError::RenameToUnknownPackage { old, new })
} else {
None
}
});
let mut cycle_problems = collections::BTreeSet::new();
for (pkg_name, pkg) in &self.pkg {
for target_name in pkg.target.keys() {
for cycle in validate_depenency_cycles(
self,
ArtefactQuery::new(pkg_name, target_name),
) {
cycle_problems.insert(cycle);
}
}
}
let cycle_problems = cycle_problems
.into_iter()
.map(ValidationError::DependencyLoop);
pkg_problems
.chain(rename_problems)
.chain(cycle_problems)
.collect()
}
}
impl Default for Channel {
fn default() -> Self {
Channel::new()
}
}
impl FromStr for Channel {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s)
}
}
impl fmt::Display for Channel {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"Rust release channel for version {}, dated {}",
self.pkg
.get("rust")
.map(|pkg| pkg.version.as_str())
.unwrap_or("<unknown>"),
self.date,
)
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, PartialOrd,
Ord, Hash)]
pub struct Package {
pub version: String,
pub git_commit_hash: Option<String>,
#[serde(skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
pub target: collections::BTreeMap<String, Artefact>,
}
impl Package {
pub fn new(version: String) -> Package {
Package {
version: version,
git_commit_hash: None,
target: collections::BTreeMap::new(),
}
}
fn validate<'a>(
&'a self,
channel: &'a Channel,
pkg: &'a str,
) -> Vec<ValidationError<'a>> {
self.target
.iter()
.flat_map(|(target, artefact)| {
artefact.validate(channel, pkg, target)
})
.collect()
}
}
impl fmt::Display for Package {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"Package version {} for {} target(s)",
self.version,
self.target.len(),
)
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Artefact {
pub standalone: collections::BTreeMap<ArchiveFormat, ArchiveSource>,
pub components: Vec<ArtefactToken>,
pub extensions: Vec<ArtefactToken>,
}
impl Artefact {
pub fn new() -> Artefact {
Default::default()
}
fn validate<'a>(
&'a self,
channel: &'a Channel,
pkg: &'a str,
target: &'a str,
) -> Vec<ValidationError<'a>> {
let mut res = Vec::new();
for token in self.components.iter() {
let component_pkg = channel.pkg.get(&token.pkg);
let component_target = component_pkg
.clone()
.and_then(|pkg| pkg.target.get(&token.target));
match (component_pkg, component_target) {
(None, _) => {
res.push(ValidationError::ArtefactHasUnknownComponent {
artefact: ArtefactQuery::new(pkg, target),
component_pkg: &token.pkg,
})
}
(Some(_), None) => res.push(
ValidationError::ArtefactComponentHasUnknownTarget {
artefact: ArtefactQuery::new(pkg, target),
component: ArtefactQuery::new(
&token.pkg,
&token.target,
),
},
),
_ => (),
}
}
for token in self.extensions.iter() {
let extension_pkg = channel.pkg.get(&token.pkg);
let extension_target = extension_pkg
.clone()
.and_then(|pkg| pkg.target.get(&token.target));
match (extension_pkg, extension_target) {
(None, _) => {
res.push(ValidationError::ArtefactHasUnknownExtension {
artefact: ArtefactQuery::new(pkg, target),
extension_pkg: &token.pkg,
})
}
(Some(_), None) => res.push(
ValidationError::ArtefactExtensionHasUnknownTarget {
artefact: ArtefactQuery::new(pkg, target),
extension: ArtefactQuery::new(
&token.pkg,
&token.target,
),
},
),
_ => (),
}
}
res
}
}
impl fmt::Display for Artefact {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"Artefact in {} format(s), with {} component(s) and {} extension(s)",
self.standalone.len(),
self.components.len(),
self.extensions.len(),
)
}
}
fn sort_cycle<T>(input: &[T]) -> Vec<T>
where
T: Ord + Clone,
{
let len = input.len();
if len == 0 {
return Vec::new();
}
let index_min = input
.iter()
.enumerate()
.map(|(index, item)| (item, index))
.min()
.map(|(_item, index)| index)
.expect("We already handled the zero-item case");
let mut res = Vec::with_capacity(len);
res.extend_from_slice(&input[index_min..]);
res.extend_from_slice(&input[0..index_min]);
res
}
fn validate_depenency_cycles<'a>(
channel: &'a Channel,
query: ArtefactQuery<'a>,
) -> collections::BTreeSet<Vec<ArtefactQuery<'a>>> {
let mut to_check = vec![(Vec::new(), query)];
let mut res = collections::BTreeSet::new();
while let Some((ancestors, query)) = to_check.pop() {
let cycle_start =
ancestors.iter().position(|ancestor| ancestor == &query);
match cycle_start {
Some(index) => {
res.insert(sort_cycle(&ancestors[index..]));
}
None => {
channel.lookup_artefact(query).map(|artefact| {
for each in &artefact.components {
let mut dep_path = ancestors.clone();
dep_path.push(query);
to_check.push((dep_path, each.into()));
}
});
}
};
}
res
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ArchiveSource {
pub url: url::Url,
pub hash: String,
}
impl ArchiveSource {
pub fn new(url: url::Url, hash: String) -> ArchiveSource {
ArchiveSource { url, hash }
}
}
impl fmt::Display for ArchiveSource {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}>", self.url)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ArchiveFormat {
TarGzip,
TarXz,
#[doc(hidden)]
ReservedForFutureExpansion,
}
impl fmt::Display for ArchiveFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
&ArchiveFormat::TarGzip => write!(f, ".tar.gz"),
&ArchiveFormat::TarXz => write!(f, ".tar.xz"),
_ => write!(f, "<unknown>"),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, PartialOrd,
Ord, Hash)]
pub struct ArtefactToken {
pub pkg: String,
pub target: String,
}
impl ArtefactToken {
pub fn new(pkg: String, target: String) -> ArtefactToken {
ArtefactToken { pkg, target }
}
}
impl fmt::Display for ArtefactToken {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Package {} built for {}", self.pkg, self.target,)
}
}
impl<'a> PartialEq<ArtefactQuery<'a>> for ArtefactToken {
fn eq(&self, other: &ArtefactQuery<'a>) -> bool {
self.pkg == other.pkg && self.target == other.target
}
}
#[derive(Deserialize, Serialize, Debug, Copy, Clone, PartialEq, Eq,
PartialOrd, Ord, Hash)]
pub struct ArtefactQuery<'a> {
pub pkg: &'a str,
pub target: &'a str,
}
impl<'a> ArtefactQuery<'a> {
pub fn new(pkg: &'a str, target: &'a str) -> ArtefactQuery<'a> {
ArtefactQuery { pkg, target }
}
pub fn to_token(&self) -> ArtefactToken {
ArtefactToken {
pkg: self.pkg.into(),
target: self.target.into(),
}
}
}
impl<'a> fmt::Display for ArtefactQuery<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "Package {} built for {}", self.pkg, self.target,)
}
}
impl<'a> PartialEq<ArtefactToken> for ArtefactQuery<'a> {
fn eq(&self, other: &ArtefactToken) -> bool {
self.pkg == other.pkg && self.target == other.target
}
}
impl<'a> From<&'a ArtefactToken> for ArtefactQuery<'a> {
fn from(src: &'a ArtefactToken) -> ArtefactQuery<'a> {
ArtefactQuery::new(&src.pkg, &src.target)
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, PartialOrd,
Ord, Hash)]
pub enum ValidationError<'a> {
RenameToUnknownPackage {
old: &'a str,
new: &'a str,
},
RenameToItself(&'a str),
RenameExistingPackage(&'a str),
ArtefactHasUnknownComponent {
artefact: ArtefactQuery<'a>,
component_pkg: &'a str,
},
ArtefactComponentHasUnknownTarget {
artefact: ArtefactQuery<'a>,
component: ArtefactQuery<'a>,
},
ArtefactHasUnknownExtension {
artefact: ArtefactQuery<'a>,
extension_pkg: &'a str,
},
ArtefactExtensionHasUnknownTarget {
artefact: ArtefactQuery<'a>,
extension: ArtefactQuery<'a>,
},
DependencyLoop(Vec<ArtefactQuery<'a>>),
#[doc(hidden)]
__Nonexhaustive,
}
impl<'a> fmt::Display for ValidationError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
&ValidationError::RenameToUnknownPackage { old, new } => write!(
f,
"Package {:?} renamed to unknown package {:?}",
old, new,
),
&ValidationError::RenameToItself(pkg) => {
write!(f, "Package {:?} renamed to the same name", pkg)
}
&ValidationError::RenameExistingPackage(pkg) => {
write!(f, "Package {:?} is renamed, but still exists", pkg)
}
&ValidationError::ArtefactHasUnknownComponent {
artefact,
component_pkg,
} => write!(
f,
"{:?} has unknown component {:?}",
artefact, component_pkg,
),
&ValidationError::ArtefactComponentHasUnknownTarget {
artefact,
component,
} => write!(
f,
"{:?} has component for unknown target {:?}",
artefact, component,
),
&ValidationError::ArtefactHasUnknownExtension {
artefact,
extension_pkg,
} => write!(
f,
"{:?} has unknown extension {:?}",
artefact, extension_pkg,
),
&ValidationError::ArtefactExtensionHasUnknownTarget {
artefact,
extension,
} => write!(
f,
"{:?} has extension for unknown target {:?}",
artefact, extension,
),
&ValidationError::DependencyLoop(ref members) => write!(
f,
"Dependency loop among packages: {}",
members
.iter()
.map(|query| format!("{} for {}", query.pkg, query.target))
.collect::<Vec<_>>()[..]
.join(", ")
),
&ValidationError::__Nonexhaustive => unreachable!(),
}
}
}
impl<'a> error::Error for ValidationError<'a> {
fn description(&self) -> &str {
match self {
&ValidationError::RenameToUnknownPackage { .. } => {
"A package was renamed to an unknown name"
}
&ValidationError::RenameToItself(_) => {
"A package was renamed to the same name"
}
&ValidationError::RenameExistingPackage(_) => {
"A package was renamed, but still exists under the old name"
}
&ValidationError::ArtefactHasUnknownComponent { .. } => {
"An artefact has an unknown component"
}
&ValidationError::ArtefactComponentHasUnknownTarget { .. } => {
"An artefact has a component with an unknown target"
}
&ValidationError::ArtefactHasUnknownExtension { .. } => {
"An artefact has an unknown extension"
}
&ValidationError::ArtefactExtensionHasUnknownTarget { .. } => {
"An artefact has an extension with an unknown target"
}
&ValidationError::DependencyLoop(_) => {
"A package has a dependency loop."
}
&ValidationError::__Nonexhaustive => unreachable!(),
}
}
}
mod serialization;
#[cfg(test)]
mod tests;