<commit_before>use futures;
use std::str;
use std::io::{self, Read, Write};
use tokio_io::{AsyncRead, AsyncWrite};
use futures::task;
use tokio_reactor::PollEvented;
use mio::{event::Evented, PollOpt, Poll, Ready, Token, SetReadiness, Registration};

struct MockHttpStream {
    data: Vec<u8>,
    pos: usize,
    ready_for_response: bool,
    task: Option<task::Task>,
    registration: Registration,
}

pub struct MockPollStream {
    stream: PollEvented<MockHttpStream>,
    set_readiness: SetReadiness
}

impl MockPollStream {
    pub fn new(data: Vec<u8>) -> MockPollStream {
        let (registration, set_readiness) = Registration::new2();
        let m = MockHttpStream {
            data: data,
            pos: 0,
            ready_for_response: false,
            task: None,
            registration: registration
        };
        set_readiness.set_readiness(Ready::writable()).unwrap();
        MockPollStream {
            stream: PollEvented::new(m),
            set_readiness: set_readiness
        }
    }
}

impl Evented for MockHttpStream {
    fn register(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt)
        -> io::Result<()>
    {
        Ok(self.registration.register(poll, token, interest, opts)?)
    }

    fn reregister(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt)
        -> io::Result<()>
    {
        Ok(self.registration.reregister(poll, token, interest, opts)?)
    }

    fn deregister(&self, _poll: &Poll) -> io::Result<()> {
        Ok(())
    }
}

impl Read for MockHttpStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if !self.ready_for_response {
            trace!("Not ready for read yet");
            self.task = Some(task::current());
            return Err(io::ErrorKind::WouldBlock.into());
        }
        trace!("Buffer size: {}, Data size: {}, Pos: {}", buf.len(), self.data.len(), self.pos);
        let n = if buf.len() < (self.data.len()-self.pos) { buf.len() } else { self.data.len()-self.pos };
        if n > 0 {
            for i in 0..n {
                buf[i] = self.data[self.pos];
                self.pos+=1;
            }
        }
        trace!("Read {} bytes: '{}'", n, str::from_utf8(&buf[..n]).unwrap_or("<bad utf-8>"));
        self.task = Some(task::current());
        Ok(n)
    }
}

impl AsyncRead for MockHttpStream {}

impl Read for MockPollStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.stream.read(buf)
    }
}

impl AsyncRead for MockPollStream {}

impl Write for MockHttpStream {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        trace!("Request data: {}", str::from_utf8(data).unwrap_or("<bad utf-8>"));
        self.ready_for_response = true;
        Ok(data.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl AsyncWrite for MockHttpStream {
    fn shutdown(&mut self) -> futures::Poll<(), io::Error> {
        Ok(().into())
    }
}

impl Write for MockPollStream {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        let ret = self.stream.write(data);
        self.set_readiness.set_readiness(Ready::readable()).unwrap();
        ret
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stream.flush()
    }
}

impl AsyncWrite for MockPollStream {
    fn shutdown(&mut self) -> futures::Poll<(), io::Error> {
        self.stream.shutdown()
    }
}<commit_msg>fix(streams): set clear_write_ready<commit_after>use futures;
use std::str;
use std::io::{self, Read, Write};
use tokio_io::{AsyncRead, AsyncWrite};
use futures::task;
use tokio_reactor::PollEvented;
use mio::{event::Evented, PollOpt, Poll, Ready, Token, SetReadiness, Registration};

struct MockHttpStream {
    data: Vec<u8>,
    pos: usize,
    ready_for_response: bool,
    task: Option<task::Task>,
    registration: Registration,
}

pub struct MockPollStream {
    stream: PollEvented<MockHttpStream>,
    set_readiness: SetReadiness
}

impl MockPollStream {
    pub fn new(data: Vec<u8>) -> MockPollStream {
        let (registration, set_readiness) = Registration::new2();
        let m = MockHttpStream {
            data: data,
            pos: 0,
            ready_for_response: false,
            task: None,
            registration: registration
        };
        set_readiness.set_readiness(Ready::writable()).unwrap();
        MockPollStream {
            stream: PollEvented::new(m),
            set_readiness: set_readiness
        }
    }
}

impl Evented for MockHttpStream {
    fn register(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt)
        -> io::Result<()>
    {
        Ok(self.registration.register(poll, token, interest, opts)?)
    }

    fn reregister(&self, poll: &Poll, token: Token, interest: Ready, opts: PollOpt)
        -> io::Result<()>
    {
        Ok(self.registration.reregister(poll, token, interest, opts)?)
    }

    fn deregister(&self, _poll: &Poll) -> io::Result<()> {
        Ok(())
    }
}

impl Read for MockHttpStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if !self.ready_for_response {
            trace!("Not ready for read yet");
            self.task = Some(task::current());
            return Err(io::ErrorKind::WouldBlock.into());
        }
        trace!("Buffer size: {}, Data size: {}, Pos: {}", buf.len(), self.data.len(), self.pos);
        let n = if buf.len() < (self.data.len()-self.pos) { buf.len() } else { self.data.len()-self.pos };
        if n > 0 {
            for i in 0..n {
                buf[i] = self.data[self.pos];
                self.pos+=1;
            }
        }
        trace!("Read {} bytes: '{}'", n, str::from_utf8(&buf[..n]).unwrap_or("<bad utf-8>"));
        self.task = Some(task::current());
        Ok(n)
    }
}

impl AsyncRead for MockHttpStream {}

impl Read for MockPollStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.stream.read(buf)
    }
}

impl AsyncRead for MockPollStream {}

impl Write for MockHttpStream {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        trace!("Request data: {}", str::from_utf8(data).unwrap_or("<bad utf-8>"));
        self.ready_for_response = true;
        Ok(data.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl AsyncWrite for MockHttpStream {
    fn shutdown(&mut self) -> futures::Poll<(), io::Error> {
        Ok(().into())
    }
}

impl Write for MockPollStream {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        let ret = self.stream.write(data);
        match ret {
            Ok(r) => {
                self.set_readiness.set_readiness(Ready::readable()).unwrap();
                Ok(r)
            }

            Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
                self.stream.clear_write_ready()?;
                Err(io::Error::from(io::ErrorKind::WouldBlock))
            }

            Err(e) => Err(e),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stream.flush()
    }
}

impl AsyncWrite for MockPollStream {
    fn shutdown(&mut self) -> futures::Poll<(), io::Error> {
        self.stream.shutdown()
    }
}<|endoftext|>


<commit_before>use serde_json;
use reqwest;
use std;
use std::fs::File;
use std::io::Read;
use toml;

#[derive(Deserialize, Debug, Clone)]
pub struct Config {
    pub token: String,
    pub org: String,
    pub team: String,
    pub gid: Option<u64>,
    pub group: Option<String>,
    #[serde(default = "default_endpoint")]
    pub endpoint: String,
    #[serde(default = "default_home")]
    pub home: String,
    #[serde(default = "default_sh")]
    pub sh: String,
    #[serde(default = "default_cache_duration")]
    pub cache_duration: u64,
    #[serde(default = "default_cert_path")]
    pub cert_path: String,
    #[serde(default = "default_user_conf_path")]
    pub user_conf_path: String,
}

fn default_endpoint() -> String { String::from("https://api.github.com") }
fn default_home() -> String { String::from("/home/{}") }
fn default_sh() -> String { String::from("/bin/bash") }
fn default_cache_duration() -> u64 { 3600 }
fn default_cert_path() -> String { String::from("/etc/ssl/certs/ca-certificates.crt") }
fn default_user_conf_path() -> String { String::from("/.config/ghteam-auth.toml") }

impl Config {
    pub fn new(configpath: &std::path::Path) -> Result<Self, CliError> {
        let mut file = File::open(configpath)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(toml::from_str::<Config>(&contents)?)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserConfig {
    pub sh: Option<String>,
    pub pass: Option<String>,
}

impl UserConfig {
    #[allow(dead_code)]
    pub fn new(configpath: &std::path::Path) -> Result<Self, CliError> {
        let mut file = File::open(configpath)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(toml::from_str::<UserConfig>(&contents)?)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Team {
    pub id: u64,
    pub name: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Member {
    pub id: u64,
    pub login: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PublicKey {
    pub id: u64,
    pub key: String,
}

#[derive(Debug)]
pub enum CliError {
    Serde(serde_json::Error),
    Reqwest(reqwest::Error),
    Io(std::io::Error),
    Toml(toml::de::Error),
}

impl From<serde_json::Error> for CliError {
    fn from(err: serde_json::Error) -> CliError { CliError::Serde(err) }
}
impl From<reqwest::Error> for CliError {
    fn from(err: reqwest::Error) -> CliError { CliError::Reqwest(err) }
}
impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> CliError { CliError::Io(err) }
}
impl From<toml::de::Error> for CliError {
    fn from(err: toml::de::Error) -> CliError { CliError::Toml(err) }
}
<commit_msg>Remove the leading slash of a relative path<commit_after>use serde_json;
use reqwest;
use std;
use std::fs::File;
use std::io::Read;
use toml;

#[derive(Deserialize, Debug, Clone)]
pub struct Config {
    pub token: String,
    pub org: String,
    pub team: String,
    pub gid: Option<u64>,
    pub group: Option<String>,
    #[serde(default = "default_endpoint")]
    pub endpoint: String,
    #[serde(default = "default_home")]
    pub home: String,
    #[serde(default = "default_sh")]
    pub sh: String,
    #[serde(default = "default_cache_duration")]
    pub cache_duration: u64,
    #[serde(default = "default_cert_path")]
    pub cert_path: String,
    #[serde(default = "default_user_conf_path")]
    pub user_conf_path: String,
}

fn default_endpoint() -> String { String::from("https://api.github.com") }
fn default_home() -> String { String::from("/home/{}") }
fn default_sh() -> String { String::from("/bin/bash") }
fn default_cache_duration() -> u64 { 3600 }
fn default_cert_path() -> String { String::from("/etc/ssl/certs/ca-certificates.crt") }
fn default_user_conf_path() -> String { String::from(".config/ghteam-auth.toml") }

impl Config {
    pub fn new(configpath: &std::path::Path) -> Result<Self, CliError> {
        let mut file = File::open(configpath)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(toml::from_str::<Config>(&contents)?)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserConfig {
    pub sh: Option<String>,
    pub pass: Option<String>,
}

impl UserConfig {
    #[allow(dead_code)]
    pub fn new(configpath: &std::path::Path) -> Result<Self, CliError> {
        let mut file = File::open(configpath)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(toml::from_str::<UserConfig>(&contents)?)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Team {
    pub id: u64,
    pub name: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Member {
    pub id: u64,
    pub login: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PublicKey {
    pub id: u64,
    pub key: String,
}

#[derive(Debug)]
pub enum CliError {
    Serde(serde_json::Error),
    Reqwest(reqwest::Error),
    Io(std::io::Error),
    Toml(toml::de::Error),
}

impl From<serde_json::Error> for CliError {
    fn from(err: serde_json::Error) -> CliError { CliError::Serde(err) }
}
impl From<reqwest::Error> for CliError {
    fn from(err: reqwest::Error) -> CliError { CliError::Reqwest(err) }
}
impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> CliError { CliError::Io(err) }
}
impl From<toml::de::Error> for CliError {
    fn from(err: toml::de::Error) -> CliError { CliError::Toml(err) }
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::Command;
use std::{env, fs};

use rustc_version::VersionMeta;
use tempdir::TempDir;
use toml::{Table, Value};

use CompilationMode;
use cargo::{Root, Rustflags};
use errors::*;
use extensions::CommandExt;
use rustc::{Src, Sysroot, Target};
use util;
use xargo::Home;
use {cargo, xargo};

#[cfg(feature = "dev")]
fn profile() -> &'static str {
    "debug"
}

#[cfg(not(feature = "dev"))]
fn profile() -> &'static str {
    "release"
}

fn build(
    cmode: &CompilationMode,
    blueprint: Blueprint,
    ctoml: &cargo::Toml,
    home: &Home,
    rustflags: &Rustflags,
    sysroot: &Sysroot,
    hash: u64,
    verbose: bool,
) -> Result<()> {
    const TOML: &'static str = r#"
[package]
authors = ["The Rust Project Developers"]
name = "sysroot"
version = "0.0.0"
"#;

    let rustlib = home.lock_rw(cmode.triple())?;
    rustlib
        .remove_siblings()
        .chain_err(|| format!("couldn't clear {}", rustlib.path().display()))?;
    let dst = rustlib.parent().join("lib");
    util::mkdir(&dst)?;

    if cmode.triple().contains("pc-windows-gnu") {
        let src = &sysroot
            .path()
            .join("lib")
            .join("rustlib")
            .join(cmode.triple())
            .join("lib");

        // These are required for linking executables/dlls
        for file in ["rsbegin.o", "rsend.o", "crt2.o", "dllcrt2.o"].iter() {
            let file_src = src.join(file);
            let file_dst = dst.join(file);
            fs::copy(&file_src, &file_dst).chain_err(|| {
                format!(
                    "couldn't copy {} to {}",
                    file_src.display(),
                    file_dst.display()
                )
            })?;
        }
    }

    for (_, stage) in blueprint.stages {
        let td = TempDir::new("xargo").chain_err(|| "couldn't create a temporary directory")?;
        let td = td.path();

        let mut stoml = TOML.to_owned();
        {
            let mut map = Table::new();

            map.insert("dependencies".to_owned(), Value::Table(stage.dependencies));
            if let Some(patch) = stage.patch {
                map.insert("patch".to_owned(), Value::Table(patch));
            }

            stoml.push_str(&Value::Table(map).to_string());
        }

        if let Some(profile) = ctoml.profile() {
            stoml.push_str(&profile.to_string())
        }

        util::write(&td.join("Cargo.toml"), &stoml)?;
        util::mkdir(&td.join("src"))?;
        util::write(&td.join("src/lib.rs"), "")?;

        let cargo = || {
            let mut cmd = Command::new("cargo");
            let mut flags = rustflags.for_xargo(home);
            flags.push_str(" -Z force-unstable-if-unmarked");
            if verbose {
                writeln!(io::stderr(), "+ RUSTFLAGS={:?}", flags).ok();
            }
            cmd.env("RUSTFLAGS", flags);
            cmd.env_remove("CARGO_TARGET_DIR");

            // As of rust-lang/cargo#4788 Cargo invokes rustc with a changed "current directory" so
            // we can't assume that such directory will be the same as the directory from which
            // Xargo was invoked. This is specially true when compiling the sysroot as the std
            // source is provided as a workspace and Cargo will change the current directory to the
            // root of the workspace when building one. To ensure rustc finds a target specification
            // file stored in the current directory we'll set `RUST_TARGET_PATH`  to the current
            // directory.
            if env::var_os("RUST_TARGET_PATH").is_none() {
                if let CompilationMode::Cross(ref target) = *cmode {
                    if let Target::Custom { ref json, .. } = *target {
                        cmd.env("RUST_TARGET_PATH", json.parent().unwrap());
                    }
                }
            }

            cmd.arg("build");

            match () {
                #[cfg(feature = "dev")]
                () => {}
                #[cfg(not(feature = "dev"))]
                () => {
                    cmd.arg("--release");
                }
            }
            cmd.arg("--manifest-path");
            cmd.arg(td.join("Cargo.toml"));
            cmd.args(&["--target", cmode.triple()]);

            if verbose {
                cmd.arg("-v");
            }

            cmd
        };

        for krate in stage.crates {
            cargo().arg("-p").arg(krate).run(verbose)?;
        }

        // Copy artifacts to Xargo sysroot
        util::cp_r(
            &td.join("target")
                .join(cmode.triple())
                .join(profile())
                .join("deps"),
            &dst,
        )?;
    }

    // Create hash file
    util::write(&rustlib.parent().join(".hash"), &hash.to_string())?;

    Ok(())
}

fn old_hash(cmode: &CompilationMode, home: &Home) -> Result<Option<u64>> {
    // FIXME this should be `lock_ro`
    let lock = home.lock_rw(cmode.triple())?;
    let hfile = lock.parent().join(".hash");

    if hfile.exists() {
        Ok(util::read(&hfile)?.parse().ok())
    } else {
        Ok(None)
    }
}

/// Computes the hash of the would-be target sysroot
///
/// This information is used to compute the hash
///
/// - Dependencies in `Xargo.toml` for a specific target
/// - RUSTFLAGS / build.rustflags / target.*.rustflags
/// - The target specification file, is any
/// - `[profile.release]` in `Cargo.toml`
/// - `rustc` commit hash
fn hash(
    cmode: &CompilationMode,
    blueprint: &Blueprint,
    rustflags: &Rustflags,
    ctoml: &cargo::Toml,
    meta: &VersionMeta,
) -> Result<u64> {
    let mut hasher = DefaultHasher::new();

    blueprint.hash(&mut hasher);

    rustflags.hash(&mut hasher);

    cmode.hash(&mut hasher)?;

    if let Some(profile) = ctoml.profile() {
        profile.hash(&mut hasher);
    }

    if let Some(ref hash) = meta.commit_hash {
        hash.hash(&mut hasher);
    }

    Ok(hasher.finish())
}

pub fn update(
    cmode: &CompilationMode,
    home: &Home,
    root: &Root,
    rustflags: &Rustflags,
    meta: &VersionMeta,
    src: &Src,
    sysroot: &Sysroot,
    verbose: bool,
) -> Result<()> {
    let ctoml = cargo::toml(root)?;
    let xtoml = xargo::toml(root)?;

    let blueprint = Blueprint::from(xtoml.as_ref(), cmode.triple(), root, &src)?;

    let hash = hash(cmode, &blueprint, rustflags, &ctoml, meta)?;

    if old_hash(cmode, home)? != Some(hash) {
        build(
            cmode,
            blueprint,
            &ctoml,
            home,
            rustflags,
            sysroot,
            hash,
            verbose,
        )?;
    }

    // copy host artifacts into the sysroot, if necessary
    if cmode.is_native() {
        return Ok(());
    }

    let lock = home.lock_rw(&meta.host)?;
    let hfile = lock.parent().join(".hash");

    let hash = meta.commit_hash.as_ref().map(|s| &**s).unwrap_or("");
    if hfile.exists() {
        if util::read(&hfile)? == hash {
            return Ok(());
        }
    }

    lock.remove_siblings()
        .chain_err(|| format!("couldn't clear {}", lock.path().display()))?;
    let dst = lock.parent().join("lib");
    util::mkdir(&dst)?;
    util::cp_r(
        &sysroot
            .path()
            .join("lib/rustlib")
            .join(&meta.host)
            .join("lib"),
        &dst,
    )?;

    let bin_dst = lock.parent().join("bin");
    util::mkdir(&bin_dst)?;
    util::cp_r(
        &sysroot
            .path()
            .join("lib/rustlib")
            .join(&meta.host)
            .join("bin"),
        &bin_dst,
    )?;

    util::write(&hfile, hash)?;

    Ok(())
}

/// Per stage dependencies
#[derive(Debug)]
pub struct Stage {
    crates: Vec<String>,
    dependencies: Table,
    patch: Option<Table>,
}

/// A sysroot that will be built in "stages"
#[derive(Debug)]
pub struct Blueprint {
    stages: BTreeMap<i64, Stage>,
}

impl Blueprint {
    fn new() -> Self {
        Blueprint {
            stages: BTreeMap::new(),
        }
    }

    fn from(toml: Option<&xargo::Toml>, target: &str, root: &Root, src: &Src) -> Result<Self> {
        let deps = match (
            toml.and_then(|t| t.dependencies()),
            toml.and_then(|t| t.target_dependencies(target)),
        ) {
            (Some(value), Some(tvalue)) => {
                let mut deps = value
                    .as_table()
                    .cloned()
                    .ok_or_else(|| format!("Xargo.toml: `dependencies` must be a table"))?;

                let more_deps = tvalue.as_table().ok_or_else(|| {
                    format!(
                        "Xargo.toml: `target.{}.dependencies` must be \
                         a table",
                        target
                    )
                })?;
                for (k, v) in more_deps {
                    if deps.insert(k.to_owned(), v.clone()).is_some() {
                        Err(format!(
                            "found duplicate dependency name {}, \
                             but all dependencies must have a \
                             unique name",
                            k
                        ))?
                    }
                }

                deps
            }
            (Some(value), None) | (None, Some(value)) => if let Some(table) = value.as_table() {
                table.clone()
            } else {
                Err(format!(
                    "Xargo.toml: target.{}.dependencies must be \
                     a table",
                    target
                ))?
            },
            (None, None) => {
                // If no dependencies were listed, we assume `core` and `compiler_builtins` as the
                // dependencies
                let mut t = BTreeMap::new();
                let mut core = BTreeMap::new();
                core.insert("stage".to_owned(), Value::Integer(0));
                t.insert("core".to_owned(), Value::Table(core));
                let mut cb = BTreeMap::new();
                cb.insert(
                    "features".to_owned(),
                    Value::Array(vec![Value::String("mem".to_owned())]),
                );
                cb.insert("stage".to_owned(), Value::Integer(1));
                t.insert(
                    "compiler_builtins".to_owned(),
                    Value::Table(cb),
                );
                t
            }
        };

        let mut blueprint = Blueprint::new();
        for (k, v) in deps {
            if let Value::Table(mut map) = v {
                let stage = if let Some(value) = map.remove("stage") {
                    value
                        .as_integer()
                        .ok_or_else(|| format!("dependencies.{}.stage must be an integer", k))?
                } else {
                    0
                };

                if let Some(path) = map.get_mut("path") {
                    let p = PathBuf::from(path.as_str()
                        .ok_or_else(|| format!("dependencies.{}.path must be a string", k))?);

                    if !p.is_absolute() {
                        *path = Value::String(
                            root.path()
                                .join(&p)
                                .canonicalize()
                                .chain_err(|| format!("couldn't canonicalize {}", p.display()))?
                                .display()
                                .to_string(),
                        );
                    }
                }

                if !map.contains_key("path") && !map.contains_key("git") {
                    // No path and no git given.  This might be in the sysroot, but if we don't find it there we assume it comes from crates.io.
                    let path = src.path().join(format!("lib{}", k));
                    if path.exists() {
                        map.insert("path".to_owned(), Value::String(path.display().to_string()));
                    }
                }

                blueprint.push(stage, k, map, src);
            } else {
                Err(format!(
                    "Xargo.toml: target.{}.dependencies.{} must be \
                     a table",
                    target, k
                ))?
            }
        }

        Ok(blueprint)
    }

    fn push(&mut self, stage: i64, krate: String, toml: Table, src: &Src) {
        let stage = self.stages.entry(stage).or_insert_with(|| Stage {
            crates: vec![],
            dependencies: Table::new(),
            patch: {
                let rustc_std_workspace_core = src.path().join("tools/rustc-std-workspace-core");
                if rustc_std_workspace_core.exists() {
                    // For a new stage, we also need to compute the patch section of the toml
                    fn make_singleton_map(key: &str, val: Value) -> Table {
                        let mut map = Table::new();
                        map.insert(key.to_owned(), val);
                        map
                    }
                    Some(make_singleton_map("crates-io", Value::Table(
                        make_singleton_map("rustc-std-workspace-core", Value::Table(
                            make_singleton_map("path", Value::String(
                                rustc_std_workspace_core.display().to_string()
                            ))
                        ))
                    )))
                } else {
                    // an old rustc, doesn't need a rustc_std_workspace_core
                    None
                }
            }
        });

        stage.dependencies.insert(krate.clone(), Value::Table(toml));
        stage.crates.push(krate);
    }

    fn hash<H>(&self, hasher: &mut H)
    where
        H: Hasher,
    {
        for stage in self.stages.values() {
            for (k, v) in stage.dependencies.iter() {
                k.hash(hasher);
                v.to_string().hash(hasher);
            }
        }
    }
}
<commit_msg>Add XARGO_KEEP_TEMP environment variable for help with debugging<commit_after>use std::collections::BTreeMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::Command;
use std::{env, fs};

use rustc_version::VersionMeta;
use tempdir::TempDir;
use toml::{Table, Value};

use CompilationMode;
use cargo::{Root, Rustflags};
use errors::*;
use extensions::CommandExt;
use rustc::{Src, Sysroot, Target};
use util;
use xargo::Home;
use {cargo, xargo};

#[cfg(feature = "dev")]
fn profile() -> &'static str {
    "debug"
}

#[cfg(not(feature = "dev"))]
fn profile() -> &'static str {
    "release"
}

fn build(
    cmode: &CompilationMode,
    blueprint: Blueprint,
    ctoml: &cargo::Toml,
    home: &Home,
    rustflags: &Rustflags,
    sysroot: &Sysroot,
    hash: u64,
    verbose: bool,
) -> Result<()> {
    const TOML: &'static str = r#"
[package]
authors = ["The Rust Project Developers"]
name = "sysroot"
version = "0.0.0"
"#;

    let rustlib = home.lock_rw(cmode.triple())?;
    rustlib
        .remove_siblings()
        .chain_err(|| format!("couldn't clear {}", rustlib.path().display()))?;
    let dst = rustlib.parent().join("lib");
    util::mkdir(&dst)?;

    if cmode.triple().contains("pc-windows-gnu") {
        let src = &sysroot
            .path()
            .join("lib")
            .join("rustlib")
            .join(cmode.triple())
            .join("lib");

        // These are required for linking executables/dlls
        for file in ["rsbegin.o", "rsend.o", "crt2.o", "dllcrt2.o"].iter() {
            let file_src = src.join(file);
            let file_dst = dst.join(file);
            fs::copy(&file_src, &file_dst).chain_err(|| {
                format!(
                    "couldn't copy {} to {}",
                    file_src.display(),
                    file_dst.display()
                )
            })?;
        }
    }

    for (_, stage) in blueprint.stages {
        let td = TempDir::new("xargo").chain_err(|| "couldn't create a temporary directory")?;
        let tdp;
        let td = if env::var_os("XARGO_KEEP_TEMP").is_some() {
            tdp = td.into_path();
            &tdp
        } else {
            td.path()
        };

        let mut stoml = TOML.to_owned();
        {
            let mut map = Table::new();

            map.insert("dependencies".to_owned(), Value::Table(stage.dependencies));
            if let Some(patch) = stage.patch {
                map.insert("patch".to_owned(), Value::Table(patch));
            }

            stoml.push_str(&Value::Table(map).to_string());
        }

        if let Some(profile) = ctoml.profile() {
            stoml.push_str(&profile.to_string())
        }

        util::write(&td.join("Cargo.toml"), &stoml)?;
        util::mkdir(&td.join("src"))?;
        util::write(&td.join("src/lib.rs"), "")?;

        let cargo = || {
            let mut cmd = Command::new("cargo");
            let mut flags = rustflags.for_xargo(home);
            flags.push_str(" -Z force-unstable-if-unmarked");
            if verbose {
                writeln!(io::stderr(), "+ RUSTFLAGS={:?}", flags).ok();
            }
            cmd.env("RUSTFLAGS", flags);
            cmd.env_remove("CARGO_TARGET_DIR");

            // As of rust-lang/cargo#4788 Cargo invokes rustc with a changed "current directory" so
            // we can't assume that such directory will be the same as the directory from which
            // Xargo was invoked. This is specially true when compiling the sysroot as the std
            // source is provided as a workspace and Cargo will change the current directory to the
            // root of the workspace when building one. To ensure rustc finds a target specification
            // file stored in the current directory we'll set `RUST_TARGET_PATH`  to the current
            // directory.
            if env::var_os("RUST_TARGET_PATH").is_none() {
                if let CompilationMode::Cross(ref target) = *cmode {
                    if let Target::Custom { ref json, .. } = *target {
                        cmd.env("RUST_TARGET_PATH", json.parent().unwrap());
                    }
                }
            }

            cmd.arg("build");

            match () {
                #[cfg(feature = "dev")]
                () => {}
                #[cfg(not(feature = "dev"))]
                () => {
                    cmd.arg("--release");
                }
            }
            cmd.arg("--manifest-path");
            cmd.arg(td.join("Cargo.toml"));
            cmd.args(&["--target", cmode.triple()]);

            if verbose {
                cmd.arg("-v");
            }

            cmd
        };

        for krate in stage.crates {
            cargo().arg("-p").arg(krate).run(verbose)?;
        }

        // Copy artifacts to Xargo sysroot
        util::cp_r(
            &td.join("target")
                .join(cmode.triple())
                .join(profile())
                .join("deps"),
            &dst,
        )?;
    }

    // Create hash file
    util::write(&rustlib.parent().join(".hash"), &hash.to_string())?;

    Ok(())
}

fn old_hash(cmode: &CompilationMode, home: &Home) -> Result<Option<u64>> {
    // FIXME this should be `lock_ro`
    let lock = home.lock_rw(cmode.triple())?;
    let hfile = lock.parent().join(".hash");

    if hfile.exists() {
        Ok(util::read(&hfile)?.parse().ok())
    } else {
        Ok(None)
    }
}

/// Computes the hash of the would-be target sysroot
///
/// This information is used to compute the hash
///
/// - Dependencies in `Xargo.toml` for a specific target
/// - RUSTFLAGS / build.rustflags / target.*.rustflags
/// - The target specification file, is any
/// - `[profile.release]` in `Cargo.toml`
/// - `rustc` commit hash
fn hash(
    cmode: &CompilationMode,
    blueprint: &Blueprint,
    rustflags: &Rustflags,
    ctoml: &cargo::Toml,
    meta: &VersionMeta,
) -> Result<u64> {
    let mut hasher = DefaultHasher::new();

    blueprint.hash(&mut hasher);

    rustflags.hash(&mut hasher);

    cmode.hash(&mut hasher)?;

    if let Some(profile) = ctoml.profile() {
        profile.hash(&mut hasher);
    }

    if let Some(ref hash) = meta.commit_hash {
        hash.hash(&mut hasher);
    }

    Ok(hasher.finish())
}

pub fn update(
    cmode: &CompilationMode,
    home: &Home,
    root: &Root,
    rustflags: &Rustflags,
    meta: &VersionMeta,
    src: &Src,
    sysroot: &Sysroot,
    verbose: bool,
) -> Result<()> {
    let ctoml = cargo::toml(root)?;
    let xtoml = xargo::toml(root)?;

    let blueprint = Blueprint::from(xtoml.as_ref(), cmode.triple(), root, &src)?;

    let hash = hash(cmode, &blueprint, rustflags, &ctoml, meta)?;

    if old_hash(cmode, home)? != Some(hash) {
        build(
            cmode,
            blueprint,
            &ctoml,
            home,
            rustflags,
            sysroot,
            hash,
            verbose,
        )?;
    }

    // copy host artifacts into the sysroot, if necessary
    if cmode.is_native() {
        return Ok(());
    }

    let lock = home.lock_rw(&meta.host)?;
    let hfile = lock.parent().join(".hash");

    let hash = meta.commit_hash.as_ref().map(|s| &**s).unwrap_or("");
    if hfile.exists() {
        if util::read(&hfile)? == hash {
            return Ok(());
        }
    }

    lock.remove_siblings()
        .chain_err(|| format!("couldn't clear {}", lock.path().display()))?;
    let dst = lock.parent().join("lib");
    util::mkdir(&dst)?;
    util::cp_r(
        &sysroot
            .path()
            .join("lib/rustlib")
            .join(&meta.host)
            .join("lib"),
        &dst,
    )?;

    let bin_dst = lock.parent().join("bin");
    util::mkdir(&bin_dst)?;
    util::cp_r(
        &sysroot
            .path()
            .join("lib/rustlib")
            .join(&meta.host)
            .join("bin"),
        &bin_dst,
    )?;

    util::write(&hfile, hash)?;

    Ok(())
}

/// Per stage dependencies
#[derive(Debug)]
pub struct Stage {
    crates: Vec<String>,
    dependencies: Table,
    patch: Option<Table>,
}

/// A sysroot that will be built in "stages"
#[derive(Debug)]
pub struct Blueprint {
    stages: BTreeMap<i64, Stage>,
}

impl Blueprint {
    fn new() -> Self {
        Blueprint {
            stages: BTreeMap::new(),
        }
    }

    fn from(toml: Option<&xargo::Toml>, target: &str, root: &Root, src: &Src) -> Result<Self> {
        let deps = match (
            toml.and_then(|t| t.dependencies()),
            toml.and_then(|t| t.target_dependencies(target)),
        ) {
            (Some(value), Some(tvalue)) => {
                let mut deps = value
                    .as_table()
                    .cloned()
                    .ok_or_else(|| format!("Xargo.toml: `dependencies` must be a table"))?;

                let more_deps = tvalue.as_table().ok_or_else(|| {
                    format!(
                        "Xargo.toml: `target.{}.dependencies` must be \
                         a table",
                        target
                    )
                })?;
                for (k, v) in more_deps {
                    if deps.insert(k.to_owned(), v.clone()).is_some() {
                        Err(format!(
                            "found duplicate dependency name {}, \
                             but all dependencies must have a \
                             unique name",
                            k
                        ))?
                    }
                }

                deps
            }
            (Some(value), None) | (None, Some(value)) => if let Some(table) = value.as_table() {
                table.clone()
            } else {
                Err(format!(
                    "Xargo.toml: target.{}.dependencies must be \
                     a table",
                    target
                ))?
            },
            (None, None) => {
                // If no dependencies were listed, we assume `core` and `compiler_builtins` as the
                // dependencies
                let mut t = BTreeMap::new();
                let mut core = BTreeMap::new();
                core.insert("stage".to_owned(), Value::Integer(0));
                t.insert("core".to_owned(), Value::Table(core));
                let mut cb = BTreeMap::new();
                cb.insert(
                    "features".to_owned(),
                    Value::Array(vec![Value::String("mem".to_owned())]),
                );
                cb.insert("stage".to_owned(), Value::Integer(1));
                t.insert(
                    "compiler_builtins".to_owned(),
                    Value::Table(cb),
                );
                t
            }
        };

        let mut blueprint = Blueprint::new();
        for (k, v) in deps {
            if let Value::Table(mut map) = v {
                let stage = if let Some(value) = map.remove("stage") {
                    value
                        .as_integer()
                        .ok_or_else(|| format!("dependencies.{}.stage must be an integer", k))?
                } else {
                    0
                };

                if let Some(path) = map.get_mut("path") {
                    let p = PathBuf::from(path.as_str()
                        .ok_or_else(|| format!("dependencies.{}.path must be a string", k))?);

                    if !p.is_absolute() {
                        *path = Value::String(
                            root.path()
                                .join(&p)
                                .canonicalize()
                                .chain_err(|| format!("couldn't canonicalize {}", p.display()))?
                                .display()
                                .to_string(),
                        );
                    }
                }

                if !map.contains_key("path") && !map.contains_key("git") {
                    // No path and no git given.  This might be in the sysroot, but if we don't find it there we assume it comes from crates.io.
                    let path = src.path().join(format!("lib{}", k));
                    if path.exists() {
                        map.insert("path".to_owned(), Value::String(path.display().to_string()));
                    }
                }

                blueprint.push(stage, k, map, src);
            } else {
                Err(format!(
                    "Xargo.toml: target.{}.dependencies.{} must be \
                     a table",
                    target, k
                ))?
            }
        }

        Ok(blueprint)
    }

    fn push(&mut self, stage: i64, krate: String, toml: Table, src: &Src) {
        let stage = self.stages.entry(stage).or_insert_with(|| Stage {
            crates: vec![],
            dependencies: Table::new(),
            patch: {
                let rustc_std_workspace_core = src.path().join("tools/rustc-std-workspace-core");
                if rustc_std_workspace_core.exists() {
                    // For a new stage, we also need to compute the patch section of the toml
                    fn make_singleton_map(key: &str, val: Value) -> Table {
                        let mut map = Table::new();
                        map.insert(key.to_owned(), val);
                        map
                    }
                    Some(make_singleton_map("crates-io", Value::Table(
                        make_singleton_map("rustc-std-workspace-core", Value::Table(
                            make_singleton_map("path", Value::String(
                                rustc_std_workspace_core.display().to_string()
                            ))
                        ))
                    )))
                } else {
                    // an old rustc, doesn't need a rustc_std_workspace_core
                    None
                }
            }
        });

        stage.dependencies.insert(krate.clone(), Value::Table(toml));
        stage.crates.push(krate);
    }

    fn hash<H>(&self, hasher: &mut H)
    where
        H: Hasher,
    {
        for stage in self.stages.values() {
            for (k, v) in stage.dependencies.iter() {
                k.hash(hasher);
                v.to_string().hash(hasher);
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::process::Command;

use cargo::util::{self, CargoResult, ChainError, Config, FileLock, Filesystem};
use chrono::NaiveDate;
use curl::http;
use flate2::read::GzDecoder;
use rustc_version::{self, Channel};
use tar::Archive;
use tempdir::TempDir;
use term::color::GREEN;
use toml::Value;

use Target;

enum Source {
    Rustup(PathBuf),
    Xargo,
}

/// Create a sysroot that looks like this:
///
/// ``` text
/// ~/.xargo
/// ├── date
/// ├── lib
/// │   └── rustlib
/// │       ├── $HOST
/// │       │   └── lib
/// │       │       ├── libcore-$hash.rlib -> $SYSROOT/lib/rustlib/$HOST/lib/libcore-$hash.rlib
/// │       │       └── (..)
/// │       ├── $TARGET1
/// │       │   ├── hash
/// │       │   └── lib
/// │       │       ├── libcore-$hash.rlib
/// │       │       └── (..)
/// │       ├── $TARGET2
/// │       │   └── (..)
/// │       ├── (..)
/// │       └── $TARGETN
/// │           └── (..)
/// └── src
///     │── libcore
///     │── libstd
///     └── (..)
/// ```
///
/// Where:
///
/// - `$SYSROOT` is the current `rustc` sysroot i.e. `$(rustc --print sysroot)`
/// - `$HOST` is the current `rustc`'s host i.e. the host field in `$(rustc -Vv)`
/// - `$TARGET*` are the custom targets `xargo` is managing
///
/// The `~/.xargo` is mostly a "standard" sysroot but with extra information:
///
/// - the `hash` files which track changes in the `$TARGET`s' specification files (i.e.
/// `$TARGET.json`).
/// - the `src` directory which holds the source code of the current `rustc` (and standard crates).
/// - the `date` file which holds the build date of the current `rustc`.
pub fn create(config: &Config,
              target: &Target,
              root: &Filesystem,
              verbose: bool,
              rustflags: &[String],
              profiles: &Option<Value>)
              -> CargoResult<()> {
    let meta = rustc_version::version_meta_for(&config.rustc_info().verbose_version);

    if meta.channel != Channel::Nightly {
        return Err(util::human("Only the nightly channel is currently supported"));
    }

    let commit_date = try!(meta.commit_date
        .as_ref()
        .and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok())
        .ok_or(util::human("couldn't find/parse the commit date in/from `rustc -Vv`")));
    // XXX AFAIK this is not guaranteed to be correct, but it appears to be a good approximation.
    let build_date = commit_date.succ();
    let commit_hash = try!(meta.commit_hash
        .ok_or(util::human("couldn't find the commit hash in `rustc -Vv`")));

    let src = try!(update_source(config, &commit_hash, &build_date, root));
    try!(rebuild_sysroot(config, root, target, verbose, rustflags, profiles, src));
    try!(symlink_host_crates(config, root));

    Ok(())
}

fn update_source(config: &Config,
                 commit_hash: &str,
                 date: &NaiveDate,
                 root: &Filesystem)
                 -> CargoResult<Source> {
    const TARBALL: &'static str = "rustc-nightly-src.tar.gz";

    /// Reads the `NaiveDate` stored in `~/.xargo/date`
    fn read_date(file: &File) -> CargoResult<Option<NaiveDate>> {
        read_string(file).map(|s| NaiveDate::parse_from_str(&s, "%Y-%m-%d").ok())
    }

    fn read_string(mut file: &File) -> CargoResult<String> {
        let mut s = String::new();
        try!(file.read_to_string(&mut s));
        Ok(s)
    }

    fn write_to_lockfile(lock: &FileLock, contents: &str) -> CargoResult<()> {
        // NOTE locked files are open in append mode, we need to "rewind" to the start before
        // writing
        let mut file = lock.file();
        try!(file.seek(SeekFrom::Start(0)));
        try!(file.set_len(0));
        try!(file.write_all(contents.as_bytes()));
        Ok(())
    }

    fn download(config: &Config, date: &NaiveDate) -> CargoResult<http::Response> {
        const B_PER_S: usize = 1;
        const MS: usize = 1_000;
        const S: usize = 1;

        // NOTE Got these settings from cargo (src/cargo/ops/registry.rs)
        let mut handle = http::handle()
            .timeout(0)
            .connect_timeout(30 * MS)
            .low_speed_limit(10 * B_PER_S)
            .low_speed_timeout(30 * S);;

        let url = format!("https://static.rust-lang.org/dist/{}/{}",
                          date.format("%Y-%m-%d"),
                          TARBALL);

        try!(config.shell().err().say_status("Downloading", &url, GREEN, true));
        let resp = try!(handle.get(url).follow_redirects(true).exec());

        let code = resp.get_code();
        if code != 200 {
            return Err(util::human(format!("HTTP error got {}, expected 200", code)));
        }

        Ok(resp)
    }

    fn unpack(config: &Config, tarball: http::Response, root: &Path) -> CargoResult<()> {
        try!(config.shell().err().say_status("Unpacking", TARBALL, GREEN, true));

        let src_dir = &root.join("src");
        try!(fs::create_dir(src_dir));

        let decoder = try!(GzDecoder::new(tarball.get_body()));
        let mut archive = Archive::new(decoder);
        for entry in try!(archive.entries()) {
            let mut entry = try!(entry);
            let path = {
                let path = try!(entry.path());
                let mut components = path.components();
                components.next();
                let next = components.next().and_then(|s| s.as_os_str().to_str());
                if next != Some("src") {
                    continue;
                }
                components.as_path().to_path_buf()
            };
            try!(entry.unpack(src_dir.join(path)));
        }

        Ok(())
    }

    let lock = try!(root.open_rw("date", config, "xargo"));

    let rustup_src_dir = try!(sysroot()).join("lib/rustlib/src/rust");

    if rustup_src_dir.exists() {
        let xargo_src_dir = &lock.path().parent().unwrap().join("src");

        if xargo_src_dir.exists() {
            try!(fs::remove_dir_all(xargo_src_dir));
        }

        if try!(read_string(lock.file())) != commit_hash {
            // Outdated build artifacts, remove them.
            try!(lock.remove_siblings());

            // Use the commit hash as a "timestamp" to detect rustc updates
            try!(write_to_lockfile(&lock, commit_hash));
        }

        return Ok(Source::Rustup(rustup_src_dir));
    }

    if try!(read_date(lock.file())).as_ref() == Some(date) {
        // Source is up to date
        return Ok(Source::Xargo);
    }

    try!(lock.remove_siblings());
    let tarball = try!(download(config, date)
        .chain_error(|| util::human("Couldn't fetch Rust source tarball")));
    try!(unpack(config, tarball, lock.parent())
        .chain_error(|| util::human("Couldn't unpack Rust source tarball")));

    // Leave a timestamp around to indicate how old this source is
    try!(write_to_lockfile(&lock, &date.format("%Y-%m-%d").to_string()));

    Ok(Source::Xargo)
}

fn rebuild_sysroot(config: &Config,
                   root: &Filesystem,
                   target: &Target,
                   verbose: bool,
                   rustflags: &[String],
                   profiles: &Option<Value>,
                   src: Source)
                   -> CargoResult<()> {
    /// Reads the hash stored in `~/.xargo/lib/rustlib/$TARGET/hash`
    fn read_hash(mut file: &File) -> CargoResult<Option<u64>> {
        let hash = &mut String::new();
        try!(file.read_to_string(hash));
        Ok(hash.parse().ok())
    }

    const CRATES: &'static [&'static str] = &["collections", "rand"];
    const NO_ATOMICS_CRATES: &'static [&'static str] = &["rustc_unicode"];

    let outer_lock = try!(root.open_ro("date", config, "xargo"));
    let lock = try!(root.open_rw(format!("lib/rustlib/{}/hash", target.triple),
                                 config,
                                 &format!("xargo/{}", target.triple)));
    let root = outer_lock.parent();

    let mut hasher = target.hasher.clone();
    rustflags.hash(&mut hasher);
    if let Some(profiles) = profiles.as_ref() {
        profiles.to_string().hash(&mut hasher);
    }
    let hash = hasher.finish();
    if try!(read_hash(lock.file())) == Some(hash) {
        // Target specification file unchanged
        return Ok(());
    }

    let lib_dir = &lock.parent().join("lib");
    try!(config.shell().err().say_status("Compiling",
                                         format!("sysroot for {}", target.triple),
                                         GREEN,
                                         true));

    let td = try!(TempDir::new("xargo"));
    let td = td.path();

    // Create Cargo project
    try!(fs::create_dir(td.join("src")));
    try!(fs::copy(&target.path, td.join(target.path.file_name().unwrap())));
    try!(File::create(td.join("src/lib.rs")));
    let toml = &mut format!("[package]
name = 'sysroot'
version = '0.0.0'

{}

[dependencies]
", profiles.as_ref().map(|t| t.to_string()).unwrap_or(String::new()));
    let src_dir = &match src {
        Source::Rustup(dir) => dir.join("src"),
        Source::Xargo => root.join("src"),
    };
    for krate in CRATES {
        toml.push_str(&format!("{} = {{ path = '{}' }}\n",
                               krate,
                               src_dir.join(format!("lib{}", krate)).display()))
    }
    try!(try!(File::create(td.join("Cargo.toml"))).write_all(toml.as_bytes()));
    if !rustflags.is_empty() {
        try!(fs::create_dir(td.join(".cargo")));
        try!(try!(File::create(td.join(".cargo/config")))
            .write_all(format!("[build]\nrustflags = {:?}", rustflags).as_bytes()));
    }

    // Build Cargo project
    let cargo = &mut Command::new("cargo");
    cargo.args(&["build", "--release", "--target"]);
    cargo.arg(&target.triple);
    if verbose {
        cargo.arg("--verbose");
    }
    if target.spec.get("max-atomic-width").map(|w| w.as_u64() == Some(0)) == Some(true) {
        for krate in NO_ATOMICS_CRATES {
            cargo.args(&["-p", krate]);
        }
    } else {
        for krate in CRATES {
            cargo.args(&["-p", krate]);
        }
    }
    cargo.env("CARGO_TARGET_DIR", td.join("target"));
    cargo.arg("--manifest-path").arg(td.join("Cargo.toml"));
    let status = try!(cargo.status());

    if !status.success() {
        return Err(util::human("`cargo` process didn't exit successfully"));
    }

    // Copy build artifacts
    if lib_dir.exists() {
        try!(fs::remove_dir_all(lib_dir));
    }
    let dst = lib_dir;
    try!(fs::create_dir_all(dst));
    for entry in try!(fs::read_dir(td.join(format!("target/{}/release/deps", target.triple)))) {
        let src = &try!(entry).path();
        try!(fs::copy(src, dst.join(src.file_name().unwrap())));
    }

    let mut file = lock.file();
    try!(file.seek(SeekFrom::Start(0)));
    try!(file.set_len(0));
    try!(file.write_all(hash.to_string().as_bytes()));

    Ok(())
}

fn symlink_host_crates(config: &Config, root: &Filesystem) -> CargoResult<()> {
    let _outer_lock = try!(root.open_ro("date", config, "xargo"));
    let host = &config.rustc_info().host;
    let lock = try!(root.open_rw(format!("lib/rustlib/{}/sentinel", host),
                                 config,
                                 &format!("xargo/{}", host)));
    let dst = &lock.parent().join("lib");

    if dst.exists() {
        return Ok(());
    }

    try!(fs::create_dir_all(dst));
    let src = try!(sysroot()).join(format!("lib/rustlib/{}/lib", host));

    for entry in try!(fs::read_dir(src)) {
        let src = &try!(entry).path();

        try!(fs::hard_link(src, dst.join(src.file_name().unwrap())));
    }

    Ok(())
}

fn sysroot() -> CargoResult<PathBuf> {
    let mut sysroot = try!(String::from_utf8(try!(Command::new("rustc")
                .args(&["--print", "sysroot"])
                .output())
            .stdout)
        .map_err(|_| util::human("output of `rustc --print sysroot` is not UTF-8")));

    while sysroot.ends_with('\n') {
        sysroot.pop();
    }

    Ok(PathBuf::from(sysroot))
}
<commit_msg>cargo fmt<commit_after>use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::process::Command;

use cargo::util::{self, CargoResult, ChainError, Config, FileLock, Filesystem};
use chrono::NaiveDate;
use curl::http;
use flate2::read::GzDecoder;
use rustc_version::{self, Channel};
use tar::Archive;
use tempdir::TempDir;
use term::color::GREEN;
use toml::Value;

use Target;

enum Source {
    Rustup(PathBuf),
    Xargo,
}

/// Create a sysroot that looks like this:
///
/// ``` text
/// ~/.xargo
/// ├── date
/// ├── lib
/// │   └── rustlib
/// │       ├── $HOST
/// │       │   └── lib
/// │       │       ├── libcore-$hash.rlib -> $SYSROOT/lib/rustlib/$HOST/lib/libcore-$hash.rlib
/// │       │       └── (..)
/// │       ├── $TARGET1
/// │       │   ├── hash
/// │       │   └── lib
/// │       │       ├── libcore-$hash.rlib
/// │       │       └── (..)
/// │       ├── $TARGET2
/// │       │   └── (..)
/// │       ├── (..)
/// │       └── $TARGETN
/// │           └── (..)
/// └── src
///     │── libcore
///     │── libstd
///     └── (..)
/// ```
///
/// Where:
///
/// - `$SYSROOT` is the current `rustc` sysroot i.e. `$(rustc --print sysroot)`
/// - `$HOST` is the current `rustc`'s host i.e. the host field in `$(rustc -Vv)`
/// - `$TARGET*` are the custom targets `xargo` is managing
///
/// The `~/.xargo` is mostly a "standard" sysroot but with extra information:
///
/// - the `hash` files which track changes in the `$TARGET`s' specification files (i.e.
/// `$TARGET.json`).
/// - the `src` directory which holds the source code of the current `rustc` (and standard crates).
/// - the `date` file which holds the build date of the current `rustc`.
pub fn create(config: &Config,
              target: &Target,
              root: &Filesystem,
              verbose: bool,
              rustflags: &[String],
              profiles: &Option<Value>)
              -> CargoResult<()> {
    let meta = rustc_version::version_meta_for(&config.rustc_info().verbose_version);

    if meta.channel != Channel::Nightly {
        return Err(util::human("Only the nightly channel is currently supported"));
    }

    let commit_date = try!(meta.commit_date
        .as_ref()
        .and_then(|s| NaiveDate::parse_from_str(s, "%Y-%m-%d").ok())
        .ok_or(util::human("couldn't find/parse the commit date in/from `rustc -Vv`")));
    // XXX AFAIK this is not guaranteed to be correct, but it appears to be a good approximation.
    let build_date = commit_date.succ();
    let commit_hash = try!(meta.commit_hash
        .ok_or(util::human("couldn't find the commit hash in `rustc -Vv`")));

    let src = try!(update_source(config, &commit_hash, &build_date, root));
    try!(rebuild_sysroot(config, root, target, verbose, rustflags, profiles, src));
    try!(symlink_host_crates(config, root));

    Ok(())
}

fn update_source(config: &Config,
                 commit_hash: &str,
                 date: &NaiveDate,
                 root: &Filesystem)
                 -> CargoResult<Source> {
    const TARBALL: &'static str = "rustc-nightly-src.tar.gz";

    /// Reads the `NaiveDate` stored in `~/.xargo/date`
    fn read_date(file: &File) -> CargoResult<Option<NaiveDate>> {
        read_string(file).map(|s| NaiveDate::parse_from_str(&s, "%Y-%m-%d").ok())
    }

    fn read_string(mut file: &File) -> CargoResult<String> {
        let mut s = String::new();
        try!(file.read_to_string(&mut s));
        Ok(s)
    }

    fn write_to_lockfile(lock: &FileLock, contents: &str) -> CargoResult<()> {
        // NOTE locked files are open in append mode, we need to "rewind" to the start before
        // writing
        let mut file = lock.file();
        try!(file.seek(SeekFrom::Start(0)));
        try!(file.set_len(0));
        try!(file.write_all(contents.as_bytes()));
        Ok(())
    }

    fn download(config: &Config, date: &NaiveDate) -> CargoResult<http::Response> {
        const B_PER_S: usize = 1;
        const MS: usize = 1_000;
        const S: usize = 1;

        // NOTE Got these settings from cargo (src/cargo/ops/registry.rs)
        let mut handle = http::handle()
            .timeout(0)
            .connect_timeout(30 * MS)
            .low_speed_limit(10 * B_PER_S)
            .low_speed_timeout(30 * S);;

        let url = format!("https://static.rust-lang.org/dist/{}/{}",
                          date.format("%Y-%m-%d"),
                          TARBALL);

        try!(config.shell().err().say_status("Downloading", &url, GREEN, true));
        let resp = try!(handle.get(url).follow_redirects(true).exec());

        let code = resp.get_code();
        if code != 200 {
            return Err(util::human(format!("HTTP error got {}, expected 200", code)));
        }

        Ok(resp)
    }

    fn unpack(config: &Config, tarball: http::Response, root: &Path) -> CargoResult<()> {
        try!(config.shell().err().say_status("Unpacking", TARBALL, GREEN, true));

        let src_dir = &root.join("src");
        try!(fs::create_dir(src_dir));

        let decoder = try!(GzDecoder::new(tarball.get_body()));
        let mut archive = Archive::new(decoder);
        for entry in try!(archive.entries()) {
            let mut entry = try!(entry);
            let path = {
                let path = try!(entry.path());
                let mut components = path.components();
                components.next();
                let next = components.next().and_then(|s| s.as_os_str().to_str());
                if next != Some("src") {
                    continue;
                }
                components.as_path().to_path_buf()
            };
            try!(entry.unpack(src_dir.join(path)));
        }

        Ok(())
    }

    let lock = try!(root.open_rw("date", config, "xargo"));

    let rustup_src_dir = try!(sysroot()).join("lib/rustlib/src/rust");

    if rustup_src_dir.exists() {
        let xargo_src_dir = &lock.path().parent().unwrap().join("src");

        if xargo_src_dir.exists() {
            try!(fs::remove_dir_all(xargo_src_dir));
        }

        if try!(read_string(lock.file())) != commit_hash {
            // Outdated build artifacts, remove them.
            try!(lock.remove_siblings());

            // Use the commit hash as a "timestamp" to detect rustc updates
            try!(write_to_lockfile(&lock, commit_hash));
        }

        return Ok(Source::Rustup(rustup_src_dir));
    }

    if try!(read_date(lock.file())).as_ref() == Some(date) {
        // Source is up to date
        return Ok(Source::Xargo);
    }

    try!(lock.remove_siblings());
    let tarball = try!(download(config, date)
        .chain_error(|| util::human("Couldn't fetch Rust source tarball")));
    try!(unpack(config, tarball, lock.parent())
        .chain_error(|| util::human("Couldn't unpack Rust source tarball")));

    // Leave a timestamp around to indicate how old this source is
    try!(write_to_lockfile(&lock, &date.format("%Y-%m-%d").to_string()));

    Ok(Source::Xargo)
}

fn rebuild_sysroot(config: &Config,
                   root: &Filesystem,
                   target: &Target,
                   verbose: bool,
                   rustflags: &[String],
                   profiles: &Option<Value>,
                   src: Source)
                   -> CargoResult<()> {
    /// Reads the hash stored in `~/.xargo/lib/rustlib/$TARGET/hash`
    fn read_hash(mut file: &File) -> CargoResult<Option<u64>> {
        let hash = &mut String::new();
        try!(file.read_to_string(hash));
        Ok(hash.parse().ok())
    }

    const CRATES: &'static [&'static str] = &["collections", "rand"];
    const NO_ATOMICS_CRATES: &'static [&'static str] = &["rustc_unicode"];

    let outer_lock = try!(root.open_ro("date", config, "xargo"));
    let lock = try!(root.open_rw(format!("lib/rustlib/{}/hash", target.triple),
                                 config,
                                 &format!("xargo/{}", target.triple)));
    let root = outer_lock.parent();

    let mut hasher = target.hasher.clone();
    rustflags.hash(&mut hasher);
    if let Some(profiles) = profiles.as_ref() {
        profiles.to_string().hash(&mut hasher);
    }
    let hash = hasher.finish();
    if try!(read_hash(lock.file())) == Some(hash) {
        // Target specification file unchanged
        return Ok(());
    }

    let lib_dir = &lock.parent().join("lib");
    try!(config.shell().err().say_status("Compiling",
                                         format!("sysroot for {}", target.triple),
                                         GREEN,
                                         true));

    let td = try!(TempDir::new("xargo"));
    let td = td.path();

    // Create Cargo project
    try!(fs::create_dir(td.join("src")));
    try!(fs::copy(&target.path, td.join(target.path.file_name().unwrap())));
    try!(File::create(td.join("src/lib.rs")));
    let toml = &mut format!("[package]
name = 'sysroot'
version = '0.0.0'

{}

[dependencies]
",
                            profiles.as_ref().map(|t| t.to_string()).unwrap_or(String::new()));
    let src_dir = &match src {
        Source::Rustup(dir) => dir.join("src"),
        Source::Xargo => root.join("src"),
    };
    for krate in CRATES {
        toml.push_str(&format!("{} = {{ path = '{}' }}\n",
                               krate,
                               src_dir.join(format!("lib{}", krate)).display()))
    }
    try!(try!(File::create(td.join("Cargo.toml"))).write_all(toml.as_bytes()));
    if !rustflags.is_empty() {
        try!(fs::create_dir(td.join(".cargo")));
        try!(try!(File::create(td.join(".cargo/config")))
            .write_all(format!("[build]\nrustflags = {:?}", rustflags).as_bytes()));
    }

    // Build Cargo project
    let cargo = &mut Command::new("cargo");
    cargo.args(&["build", "--release", "--target"]);
    cargo.arg(&target.triple);
    if verbose {
        cargo.arg("--verbose");
    }
    if target.spec.get("max-atomic-width").map(|w| w.as_u64() == Some(0)) == Some(true) {
        for krate in NO_ATOMICS_CRATES {
            cargo.args(&["-p", krate]);
        }
    } else {
        for krate in CRATES {
            cargo.args(&["-p", krate]);
        }
    }
    cargo.env("CARGO_TARGET_DIR", td.join("target"));
    cargo.arg("--manifest-path").arg(td.join("Cargo.toml"));
    let status = try!(cargo.status());

    if !status.success() {
        return Err(util::human("`cargo` process didn't exit successfully"));
    }

    // Copy build artifacts
    if lib_dir.exists() {
        try!(fs::remove_dir_all(lib_dir));
    }
    let dst = lib_dir;
    try!(fs::create_dir_all(dst));
    for entry in try!(fs::read_dir(td.join(format!("target/{}/release/deps", target.triple)))) {
        let src = &try!(entry).path();
        try!(fs::copy(src, dst.join(src.file_name().unwrap())));
    }

    let mut file = lock.file();
    try!(file.seek(SeekFrom::Start(0)));
    try!(file.set_len(0));
    try!(file.write_all(hash.to_string().as_bytes()));

    Ok(())
}

fn symlink_host_crates(config: &Config, root: &Filesystem) -> CargoResult<()> {
    let _outer_lock = try!(root.open_ro("date", config, "xargo"));
    let host = &config.rustc_info().host;
    let lock = try!(root.open_rw(format!("lib/rustlib/{}/sentinel", host),
                                 config,
                                 &format!("xargo/{}", host)));
    let dst = &lock.parent().join("lib");

    if dst.exists() {
        return Ok(());
    }

    try!(fs::create_dir_all(dst));
    let src = try!(sysroot()).join(format!("lib/rustlib/{}/lib", host));

    for entry in try!(fs::read_dir(src)) {
        let src = &try!(entry).path();

        try!(fs::hard_link(src, dst.join(src.file_name().unwrap())));
    }

    Ok(())
}

fn sysroot() -> CargoResult<PathBuf> {
    let mut sysroot = try!(String::from_utf8(try!(Command::new("rustc")
                .args(&["--print", "sysroot"])
                .output())
            .stdout)
        .map_err(|_| util::human("output of `rustc --print sysroot` is not UTF-8")));

    while sysroot.ends_with('\n') {
        sysroot.pop();
    }

    Ok(PathBuf::from(sysroot))
}
<|endoftext|>


<commit_before>//! Common types for the [push notifications module][push]
//!
//! [push]: https://matrix.org/docs/spec/client_server/r0.6.1#id89
//!
//! ## Understanding the types of this module
//!
//! Push rules are grouped in `RuleSet`s, and are grouped in five kinds (for
//! more details about the different kind of rules, see the `Ruleset` documentation,
//! or the specification). These five kinds are:
//!
//! - content rules
//! - override rules
//! - underride rules
//! - room rules
//! - sender rules
//!
//! Each of these kind of rule has a corresponding type that is
//! just a wrapper around another type:
//!
//! - `SimplePushRule` for room and sender rules
//! - `ConditionalPushRule` for override and underride rules: push rules that may depend on a
//!   condition
//! - `PatternedPushRules` for content rules, that can filter events based on a pattern to trigger
//!   the rule or not
//!
//! Having these wrapper types allows to tell at the type level what kind of rule you are
//! handling, and makes sure the `Ruleset::add` method adds your rule to the correct field
//! of `Ruleset`, and that rules that are not of the same kind are never mixed even if they share
//! the same representation.
//!
//! It is still possible to write code that is generic over a representation by manipulating
//! `SimplePushRule`, `ConditonalPushRule` or `PatternedPushRule` directly, instead of the wrappers.
//!
//! There is also the `AnyPushRule` type that is the most generic form of push rule, with all
//! the possible fields.

use std::collections::BTreeSet;

use ruma_common::StringEnum;
use serde::{Deserialize, Serialize};

mod action;
mod any_push_rule;
mod condition;
mod predefined;

pub use self::{
    action::{Action, Tweak},
    any_push_rule::{AnyPushRule, MissingConditionsError, MissingPatternError},
    condition::{ComparisonOperator, PushCondition, RoomMemberCountIs},
};

/// A push ruleset scopes a set of rules according to some criteria.
///
/// For example, some rules may only be applied for messages from a particular sender, a particular
/// room, or by default. The push ruleset contains the entire set of scopes and rules.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Ruleset {
    /// These rules configure behavior for (unencrypted) messages that match certain patterns.
    pub content: BTreeSet<ContentPushRule>,

    /// These user-configured rules are given the highest priority.
    ///
    /// This field is named `override_` instead of `override` because the latter is a reserved
    /// keyword in Rust.
    #[serde(rename = "override")]
    pub override_: BTreeSet<OverridePushRule>,

    /// These rules change the behavior of all messages for a given room.
    pub room: BTreeSet<RoomPushRule>,

    /// These rules configure notification behavior for messages from a specific Matrix user ID.
    pub sender: BTreeSet<SenderPushRule>,

    /// These rules are identical to override rules, but have a lower priority than `content`,
    /// `room` and `sender` rules.
    pub underride: BTreeSet<UnderridePushRule>,
}

impl Ruleset {
    /// Creates an empty `Ruleset`.
    pub fn new() -> Self {
        Default::default()
    }

    /// Adds a rule to the rule set.
    ///
    /// Returns `true` if the new rule was correctly added, and `false`
    /// if a rule with the same `rule_id` is already present for this kind
    /// of rule.
    pub fn add<R: RulesetMember>(&mut self, rule: R) -> bool {
        rule.add_to(self)
    }
}

impl IntoIterator for Ruleset {
    type Item = AnyPushRule;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.content
            .into_iter()
            .map(|x| x.0.into())
            .chain(self.override_.into_iter().map(|x| x.0.into()))
            .chain(self.room.into_iter().map(|x| x.0.into()))
            .chain(self.sender.into_iter().map(|x| x.0.into()))
            .chain(self.underride.into_iter().map(|x| x.0.into()))
            .collect::<Vec<_>>()
            .into_iter()
    }
}

/// A trait for types that can be added in a Ruleset
pub trait RulesetMember: private::Sealed {
    /// Adds a value in the correct field of a Ruleset.
    fn add_to(self, ruleset: &mut Ruleset) -> bool;
}

mod private {
    // See <https://rust-lang.github.io/api-guidelines/future-proofing.html>
    pub trait Sealed {}
    impl Sealed for super::OverridePushRule {}
    impl Sealed for super::UnderridePushRule {}
    impl Sealed for super::ContentPushRule {}
    impl Sealed for super::RoomPushRule {}
    impl Sealed for super::SenderPushRule {}
}

/// Creates a new wrapper type around a PushRule-like type
/// to make it possible to tell what kind of rule it is
/// even if the inner type is the same.
///
/// For instance, override and underride rules are both
/// represented as `ConditionalPushRule`s, so it is impossible
/// to tell if a rule is an override or an underride rule when
/// all you have is a `ConditionalPushRule`. With these wrapper types
/// it becomes possible.
macro_rules! rulekind {
    ($name:ident, $inner:ty, $field:ident) => {
        #[derive(Clone, Debug, Serialize, Deserialize)]
        #[doc = "Wrapper type to disambiguate the kind of the wrapped rule"]
        pub struct $name(pub $inner);

        impl RulesetMember for $name {
            fn add_to(self, ruleset: &mut Ruleset) -> bool {
                ruleset.$field.insert(self)
            }
        }

        impl Extend<$name> for Ruleset {
            fn extend<T: IntoIterator<Item=$name>>(&mut self, iter: T) {
                for rule in iter {
                    rule.add_to(self);
                }
            }
        }

        // The following trait are needed to be able to make
        // a BTreeSet of the new type

        impl Ord for $name {
            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
                self.0.rule_id.cmp(&other.0.rule_id)
            }
        }

        impl PartialOrd for $name {
            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
                Some(self.cmp(other))
            }
        }

        impl PartialEq for $name {
            fn eq(&self, other: &Self) -> bool {
                self.0.rule_id == other.0.rule_id
            }
        }

        impl Eq for $name {}
    };
}

rulekind!(OverridePushRule, ConditionalPushRule, override_);
rulekind!(UnderridePushRule, ConditionalPushRule, underride);
rulekind!(RoomPushRule, SimplePushRule, room);
rulekind!(SenderPushRule, SimplePushRule, sender);
rulekind!(ContentPushRule, PatternedPushRule, content);

/// A push rule is a single rule that states under what conditions an event should be passed onto a
/// push gateway and how the notification should be presented.
///
/// These rules are stored on the user's homeserver. They are manually configured by the user, who
/// can create and view them via the Client/Server API.
///
/// To create an instance of this type, first create a `SimplePushRuleInit` and convert it via
/// `SimplePushRule::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct SimplePushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,
}

/// Initial set of fields of `SimplePushRule`.
///
/// This struct will not be updated even if additional fields are added to `SimplePushRule` in a new
/// (non-breaking) release of the Matrix specification.
#[derive(Debug)]
pub struct SimplePushRuleInit {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,
}

impl From<SimplePushRuleInit> for SimplePushRule {
    fn from(init: SimplePushRuleInit) -> Self {
        let SimplePushRuleInit { actions, default, enabled, rule_id } = init;
        Self { actions, default, enabled, rule_id }
    }
}

/// Like `SimplePushRule`, but with an additional `conditions` field.
///
/// Only applicable to underride and override rules.
///
/// To create an instance of this type, first create a `ConditionalPushRuleInit` and convert it via
/// `ConditionalPushRule::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ConditionalPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an
    /// event.
    ///
    /// A rule with no conditions always matches.
    pub conditions: Vec<PushCondition>,
}

/// Initial set of fields of `ConditionalPushRule`.
///
/// This struct will not be updated even if additional fields are added to `ConditionalPushRule` in
/// a new (non-breaking) release of the Matrix specification.
#[derive(Debug)]
pub struct ConditionalPushRuleInit {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an
    /// event.
    ///
    /// A rule with no conditions always matches.
    pub conditions: Vec<PushCondition>,
}

impl From<ConditionalPushRuleInit> for ConditionalPushRule {
    fn from(init: ConditionalPushRuleInit) -> Self {
        let ConditionalPushRuleInit { actions, default, enabled, rule_id, conditions } = init;
        Self { actions, default, enabled, rule_id, conditions }
    }
}

/// Like `SimplePushRule`, but with an additional `pattern` field.
///
/// Only applicable to content rules.
///
/// To create an instance of this type, first create a `PatternedPushRuleInit` and convert it via
/// `PatternedPushRule::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PatternedPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The glob-style pattern to match against.
    pub pattern: String,
}

/// Initial set of fields of `PatterenedPushRule`.
///
/// This struct will not be updated even if additional fields are added to `PatterenedPushRule` in a
/// new (non-breaking) release of the Matrix specification.
#[derive(Debug)]
pub struct PatternedPushRuleInit {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The glob-style pattern to match against.
    pub pattern: String,
}

impl From<PatternedPushRuleInit> for PatternedPushRule {
    fn from(init: PatternedPushRuleInit) -> Self {
        let PatternedPushRuleInit { actions, default, enabled, rule_id, pattern } = init;
        Self { actions, default, enabled, rule_id, pattern }
    }
}

/// Information for the pusher implementation itself.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PusherData {
    /// Required if the pusher's kind is http. The URL to use to send notifications to.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,

    /// The format to use when sending notifications to the Push Gateway.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<PushFormat>,
}

impl PusherData {
    /// Creates an empty `PusherData`.
    pub fn new() -> Self {
        Default::default()
    }
}

/// A special format that the homeserver should use when sending notifications to a Push Gateway.
/// Currently, only "event_id_only" is supported as of [Push Gateway API r0.1.1][spec].
///
/// [spec]: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
#[ruma_enum(rename_all = "snake_case")]
pub enum PushFormat {
    /// Require the homeserver to only send a reduced set of fields in the push.
    EventIdOnly,

    #[doc(hidden)]
    _Custom(String),
}
<commit_msg>Custom iterator for Ruleset<commit_after>//! Common types for the [push notifications module][push]
//!
//! [push]: https://matrix.org/docs/spec/client_server/r0.6.1#id89
//!
//! ## Understanding the types of this module
//!
//! Push rules are grouped in `RuleSet`s, and are grouped in five kinds (for
//! more details about the different kind of rules, see the `Ruleset` documentation,
//! or the specification). These five kinds are:
//!
//! - content rules
//! - override rules
//! - underride rules
//! - room rules
//! - sender rules
//!
//! Each of these kind of rule has a corresponding type that is
//! just a wrapper around another type:
//!
//! - `SimplePushRule` for room and sender rules
//! - `ConditionalPushRule` for override and underride rules: push rules that may depend on a
//!   condition
//! - `PatternedPushRules` for content rules, that can filter events based on a pattern to trigger
//!   the rule or not
//!
//! Having these wrapper types allows to tell at the type level what kind of rule you are
//! handling, and makes sure the `Ruleset::add` method adds your rule to the correct field
//! of `Ruleset`, and that rules that are not of the same kind are never mixed even if they share
//! the same representation.
//!
//! It is still possible to write code that is generic over a representation by manipulating
//! `SimplePushRule`, `ConditonalPushRule` or `PatternedPushRule` directly, instead of the wrappers.
//!
//! There is also the `AnyPushRule` type that is the most generic form of push rule, with all
//! the possible fields.

use std::collections::btree_set::{BTreeSet, IntoIter as BTreeSetIter};

use ruma_common::StringEnum;
use serde::{Deserialize, Serialize};

mod action;
mod any_push_rule;
mod condition;
mod predefined;

pub use self::{
    action::{Action, Tweak},
    any_push_rule::{AnyPushRule, MissingConditionsError, MissingPatternError},
    condition::{ComparisonOperator, PushCondition, RoomMemberCountIs},
};

/// A push ruleset scopes a set of rules according to some criteria.
///
/// For example, some rules may only be applied for messages from a particular sender, a particular
/// room, or by default. The push ruleset contains the entire set of scopes and rules.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Ruleset {
    /// These rules configure behavior for (unencrypted) messages that match certain patterns.
    pub content: BTreeSet<ContentPushRule>,

    /// These user-configured rules are given the highest priority.
    ///
    /// This field is named `override_` instead of `override` because the latter is a reserved
    /// keyword in Rust.
    #[serde(rename = "override")]
    pub override_: BTreeSet<OverridePushRule>,

    /// These rules change the behavior of all messages for a given room.
    pub room: BTreeSet<RoomPushRule>,

    /// These rules configure notification behavior for messages from a specific Matrix user ID.
    pub sender: BTreeSet<SenderPushRule>,

    /// These rules are identical to override rules, but have a lower priority than `content`,
    /// `room` and `sender` rules.
    pub underride: BTreeSet<UnderridePushRule>,
}

impl Ruleset {
    /// Creates an empty `Ruleset`.
    pub fn new() -> Self {
        Default::default()
    }

    /// Adds a rule to the rule set.
    ///
    /// Returns `true` if the new rule was correctly added, and `false`
    /// if a rule with the same `rule_id` is already present for this kind
    /// of rule.
    pub fn add<R: RulesetMember>(&mut self, rule: R) -> bool {
        rule.add_to(self)
    }
}

/// Iterator type for `Ruleset`
#[derive(Debug)]
pub struct RulesetIter {
    content: BTreeSetIter<ContentPushRule>,
    override_: BTreeSetIter<OverridePushRule>,
    room: BTreeSetIter<RoomPushRule>,
    sender: BTreeSetIter<SenderPushRule>,
    underride: BTreeSetIter<UnderridePushRule>
}

impl Iterator for RulesetIter {
    type Item = AnyPushRule;

    fn next(&mut self) -> Option<Self::Item> {
        self.content.next().map(|x| x.0.into())
            .or_else(|| self.override_.next().map(|x| x.0.into()))
            .or_else(|| self.room.next().map(|x| x.0.into()))
            .or_else(|| self.sender.next().map(|x| x.0.into()))
            .or_else(|| self.underride.next().map(|x| x.0.into()))

    }
}

impl IntoIterator for Ruleset {
    type Item = AnyPushRule;
    type IntoIter = RulesetIter;

    fn into_iter(self) -> Self::IntoIter {
        RulesetIter {
            content: self.content.into_iter(),
            override_: self.override_.into_iter(),
            room: self.room.into_iter(),
            sender: self.sender.into_iter(),
            underride: self.underride.into_iter(),
        }
    }
}

/// A trait for types that can be added in a Ruleset
pub trait RulesetMember: private::Sealed {
    /// Adds a value in the correct field of a Ruleset.
    fn add_to(self, ruleset: &mut Ruleset) -> bool;
}

mod private {
    // See <https://rust-lang.github.io/api-guidelines/future-proofing.html>
    pub trait Sealed {}
    impl Sealed for super::OverridePushRule {}
    impl Sealed for super::UnderridePushRule {}
    impl Sealed for super::ContentPushRule {}
    impl Sealed for super::RoomPushRule {}
    impl Sealed for super::SenderPushRule {}
}

/// Creates a new wrapper type around a PushRule-like type
/// to make it possible to tell what kind of rule it is
/// even if the inner type is the same.
///
/// For instance, override and underride rules are both
/// represented as `ConditionalPushRule`s, so it is impossible
/// to tell if a rule is an override or an underride rule when
/// all you have is a `ConditionalPushRule`. With these wrapper types
/// it becomes possible.
macro_rules! rulekind {
    ($name:ident, $inner:ty, $field:ident) => {
        #[derive(Clone, Debug, Serialize, Deserialize)]
        #[doc = "Wrapper type to disambiguate the kind of the wrapped rule"]
        pub struct $name(pub $inner);

        impl RulesetMember for $name {
            fn add_to(self, ruleset: &mut Ruleset) -> bool {
                ruleset.$field.insert(self)
            }
        }

        impl Extend<$name> for Ruleset {
            fn extend<T: IntoIterator<Item=$name>>(&mut self, iter: T) {
                for rule in iter {
                    rule.add_to(self);
                }
            }
        }

        // The following trait are needed to be able to make
        // a BTreeSet of the new type

        impl Ord for $name {
            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
                self.0.rule_id.cmp(&other.0.rule_id)
            }
        }

        impl PartialOrd for $name {
            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
                Some(self.cmp(other))
            }
        }

        impl PartialEq for $name {
            fn eq(&self, other: &Self) -> bool {
                self.0.rule_id == other.0.rule_id
            }
        }

        impl Eq for $name {}
    };
}

rulekind!(OverridePushRule, ConditionalPushRule, override_);
rulekind!(UnderridePushRule, ConditionalPushRule, underride);
rulekind!(RoomPushRule, SimplePushRule, room);
rulekind!(SenderPushRule, SimplePushRule, sender);
rulekind!(ContentPushRule, PatternedPushRule, content);

/// A push rule is a single rule that states under what conditions an event should be passed onto a
/// push gateway and how the notification should be presented.
///
/// These rules are stored on the user's homeserver. They are manually configured by the user, who
/// can create and view them via the Client/Server API.
///
/// To create an instance of this type, first create a `SimplePushRuleInit` and convert it via
/// `SimplePushRule::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct SimplePushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,
}

/// Initial set of fields of `SimplePushRule`.
///
/// This struct will not be updated even if additional fields are added to `SimplePushRule` in a new
/// (non-breaking) release of the Matrix specification.
#[derive(Debug)]
pub struct SimplePushRuleInit {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,
}

impl From<SimplePushRuleInit> for SimplePushRule {
    fn from(init: SimplePushRuleInit) -> Self {
        let SimplePushRuleInit { actions, default, enabled, rule_id } = init;
        Self { actions, default, enabled, rule_id }
    }
}

/// Like `SimplePushRule`, but with an additional `conditions` field.
///
/// Only applicable to underride and override rules.
///
/// To create an instance of this type, first create a `ConditionalPushRuleInit` and convert it via
/// `ConditionalPushRule::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ConditionalPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an
    /// event.
    ///
    /// A rule with no conditions always matches.
    pub conditions: Vec<PushCondition>,
}

/// Initial set of fields of `ConditionalPushRule`.
///
/// This struct will not be updated even if additional fields are added to `ConditionalPushRule` in
/// a new (non-breaking) release of the Matrix specification.
#[derive(Debug)]
pub struct ConditionalPushRuleInit {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The conditions that must hold true for an event in order for a rule to be applied to an
    /// event.
    ///
    /// A rule with no conditions always matches.
    pub conditions: Vec<PushCondition>,
}

impl From<ConditionalPushRuleInit> for ConditionalPushRule {
    fn from(init: ConditionalPushRuleInit) -> Self {
        let ConditionalPushRuleInit { actions, default, enabled, rule_id, conditions } = init;
        Self { actions, default, enabled, rule_id, conditions }
    }
}

/// Like `SimplePushRule`, but with an additional `pattern` field.
///
/// Only applicable to content rules.
///
/// To create an instance of this type, first create a `PatternedPushRuleInit` and convert it via
/// `PatternedPushRule::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PatternedPushRule {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The glob-style pattern to match against.
    pub pattern: String,
}

/// Initial set of fields of `PatterenedPushRule`.
///
/// This struct will not be updated even if additional fields are added to `PatterenedPushRule` in a
/// new (non-breaking) release of the Matrix specification.
#[derive(Debug)]
pub struct PatternedPushRuleInit {
    /// Actions to determine if and how a notification is delivered for events matching this rule.
    pub actions: Vec<Action>,

    /// Whether this is a default rule, or has been set explicitly.
    pub default: bool,

    /// Whether the push rule is enabled or not.
    pub enabled: bool,

    /// The ID of this rule.
    pub rule_id: String,

    /// The glob-style pattern to match against.
    pub pattern: String,
}

impl From<PatternedPushRuleInit> for PatternedPushRule {
    fn from(init: PatternedPushRuleInit) -> Self {
        let PatternedPushRuleInit { actions, default, enabled, rule_id, pattern } = init;
        Self { actions, default, enabled, rule_id, pattern }
    }
}

/// Information for the pusher implementation itself.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PusherData {
    /// Required if the pusher's kind is http. The URL to use to send notifications to.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,

    /// The format to use when sending notifications to the Push Gateway.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<PushFormat>,
}

impl PusherData {
    /// Creates an empty `PusherData`.
    pub fn new() -> Self {
        Default::default()
    }
}

/// A special format that the homeserver should use when sending notifications to a Push Gateway.
/// Currently, only "event_id_only" is supported as of [Push Gateway API r0.1.1][spec].
///
/// [spec]: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
#[ruma_enum(rename_all = "snake_case")]
pub enum PushFormat {
    /// Require the homeserver to only send a reduced set of fields in the push.
    EventIdOnly,

    #[doc(hidden)]
    _Custom(String),
}
<|endoftext|>


<commit_before>#![crate_name = "tty"]
#![feature(rustc_private)]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * Synced with http://lingrok.org/xref/coreutils/src/tty.c
 */

extern crate getopts;
extern crate libc;

use std::ffi::CStr;
use getopts::{optflag,getopts};

#[path = "../common/util.rs"]
#[macro_use]
mod util;

extern {
    fn ttyname(filedesc: libc::c_int) -> *const libc::c_char;
    fn isatty(filedesc: libc::c_int) -> libc::c_int;
}

static NAME: &'static str = "tty";

pub fn uumain(args: Vec<String>) -> i32 {
    let options = [
        optflag("s", "silent", "print nothing, only return an exit status")
    ];

    let silent = match getopts(&args[1..], &options) {
        Ok(m) => {
            m.opt_present("s")
        },
        Err(f) => {
            println!("{}", f);
            usage();
            return 2;
        }
    };

    let tty = unsafe {
        let ptr = ttyname(libc::STDIN_FILENO);
        if !ptr.is_null() {
            String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()).to_string()
        } else {
            "".to_string()
        }
    };

    if !silent {
        if !tty.chars().all(|c| c.is_whitespace()) {
            println!("{}", tty);
        } else {
            println!("not a tty");
        }
    }

    let exit_code = unsafe {
        if isatty(libc::STDIN_FILENO) == 1 {
            libc::EXIT_SUCCESS
        } else {
            libc::EXIT_FAILURE
        }
    };

    exit_code
}

fn usage() {
    println!("usage: {} [-s]", NAME);
}
<commit_msg>Update tty's options to conform to GNU version.<commit_after>#![crate_name = "tty"]
#![feature(rustc_private)]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * Synced with http://lingrok.org/xref/coreutils/src/tty.c
 */

extern crate getopts;
extern crate libc;

use getopts::{getopts, optflag};
use std::ffi::CStr;
use std::io::Write;

#[path = "../common/util.rs"]
#[macro_use]
mod util;

extern {
    fn ttyname(filedesc: libc::c_int) -> *const libc::c_char;
    fn isatty(filedesc: libc::c_int) -> libc::c_int;
}

static NAME: &'static str = "tty";
static VERSION: &'static str = "1.0.0";

pub fn uumain(args: Vec<String>) -> i32 {
    let options = [
        optflag("s", "silent", "print nothing, only return an exit status"),
        optflag("h", "help", "display this help and exit"),
        optflag("V", "version", "output version information and exit")
    ];

    let matches = match getopts(&args[1..], &options) {
        Ok(m) => m,
        Err(f) => {
            crash!(2, "{}", f)
        }
    };

    if matches.opt_present("help") {
        let usage = getopts::usage("Print the file name of the terminal connected to standard input.", &options);

        println!("Usage: {} [OPTION]...\n{}", NAME, usage);
    } else if matches.opt_present("version") {
        println!("{} {}", NAME, VERSION);
    } else {
        let silent = matches.opt_present("s");

        let tty = unsafe {
            let ptr = ttyname(libc::STDIN_FILENO);
            if !ptr.is_null() {
                String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()).to_string()
            } else {
                "".to_string()
            }
        };

        if !silent {
            if !tty.chars().all(|c| c.is_whitespace()) {
                println!("{}", tty);
            } else {
                println!("not a tty");
            }
        }

        return unsafe {
            if isatty(libc::STDIN_FILENO) == 1 {
                libc::EXIT_SUCCESS
            } else {
                libc::EXIT_FAILURE
            }
        };
    }

    0
}
<|endoftext|>