<commit_before>//! Provides a portable interface to the filesystem.
//!
//! This module provides access to files in specific places:
//!
//! * The `resources/` subdirectory in the same directory as the
//! program executable,
//! * The `resources.zip` file in the same
//! directory as the program executable,
//! * The root folder of the  game's "save" directory which is in a
//! platform-dependent location,
//! such as `~/.local/share/<gameid>/` on Linux.  The `gameid`
//! is the the string passed to
//! `Context::load_from_conf()`; some platforms such as Windows also
//! incorporate the `author` string into the path.
//!
//! Files will be looked for in these locations in order, and the first one
//! found used.  That allows game assets to be easily distributed as an archive
//! file, but locally overridden for testing or modding simply by putting
//! altered copies of them in the game's `resources/` directory.
//!
//! Note that the file lookups WILL follow symlinks!  It is
//! more for convenience than absolute security, so don't treat it as
//! being secure.
//!
//! If you build `ggez` with the `cargo-resource-root` feature flag, it will
//! also look for a `resources/` subdirectory in the same directory as your
//! `Cargo.toml`, which can be very convenient for development.

// BUGGO: TODO: Also make it print out the searched directories when it
// can't find a file!

use std::env;
use std::fmt;
use std::io;
use std::path;

use app_dirs::*;

use GameError;
use GameResult;
use conf;
use vfs::{self, VFS};

pub use vfs::OpenOptions;

const CONFIG_NAME: &'static str = "/conf.toml";

/// A structure that contains the filesystem state and cache.
#[derive(Debug)]
pub struct Filesystem {
    vfs: vfs::OverlayFS,
    resources_path: path::PathBuf,
    zip_path: path::PathBuf,
    // user_config_path: path::PathBuf,
    user_data_path: path::PathBuf,
}

/// Represents a file, either in the filesystem, or in the resources zip file,
/// or whatever.
pub enum File {
    VfsFile(Box<vfs::VFile>),
}

impl fmt::Debug for File {
    // TODO: Make this more useful.
    // But we can't seem to get a filename out of a file,
    // soooooo.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            File::VfsFile(ref _file) => write!(f, "VfsFile"),
        }
    }
}

impl io::Read for File {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.read(buf),
        }
    }
}

impl io::Write for File {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match *self {
            File::VfsFile(ref mut f) => f.flush(),
        }
    }
}

impl Filesystem {
    /// Create a new Filesystem instance, using the given `id` and (on
    /// some platforms) the `author` as a portion of the user
    /// directory path.  This function is called automatically by
    /// ggez, the end user should never need to call it.
    pub fn new(id: &'static str, author: &'static str) -> GameResult<Filesystem> {
        let app_info = AppInfo {
            name: id,
            author: author,
        };
        let mut root_path = env::current_exe()?;

        // Ditch the filename (if any)
        if root_path.file_name().is_some() {
            root_path.pop();
        }

        // Set up VFS to merge resource path, root path, and zip path.
        let mut overlay = vfs::OverlayFS::new();

        let mut resources_path;
        let mut resources_zip_path;
        let user_data_path;
        // let user_config_path;
        // <game exe root>/resources/
        {
            resources_path = root_path.clone();
            resources_path.push("resources");
            let physfs = vfs::PhysicalFS::new(&resources_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // <root>/resources.zip
        {
            resources_zip_path = root_path.clone();
            resources_zip_path.push("resources.zip");
            if resources_zip_path.exists() {
                let zipfs = vfs::ZipFS::new(&resources_zip_path)?;
                overlay.push_back(Box::new(zipfs));
            }
        }

        // Per-user data dir,
        // ~/.local/share/whatever/
        {
            user_data_path = app_root(AppDataType::UserData, &app_info)?;
            let physfs = vfs::PhysicalFS::new(&user_data_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // Writeable local dir, ~/.config/whatever/
        // Save game dir is read-write
        {
            user_config_path = app_root(AppDataType::UserConfig, &app_info)?;
            let physfs = vfs::PhysicalFS::new(&user_config_path, false);
            overlay.push_back(Box::new(physfs));
        }

        // Cargo manifest dir!
        #[cfg(feature = "cargo-resource-root")]
        {
            if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
                let mut path = path::PathBuf::from(manifest_dir);
                path.push("resources");
                let physfs = vfs::PhysicalFS::new(&path, true);
                overlay.push_back(Box::new(physfs));
            }
        }

        let fs = Filesystem {
            vfs: overlay,
            resources_path: resources_path,
            zip_path: resources_zip_path,
            // user_config_path: user_config_path,
            user_data_path: user_data_path,
        };

        Ok(fs)
    }

    /// Opens the given path and returns the resulting `File`
    /// in read-only mode.
    pub fn open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs.open(path.as_ref()).map(|f| File::VfsFile(f))
    }

    /// Opens a file in the user directory with the given `filesystem::OpenOptions`.
    /// Note that even if you open a file read-only, it can only access
    /// files in the user directory.
    pub fn open_options<P: AsRef<path::Path>>(
        &mut self,
        path: P,
        options: &OpenOptions,
    ) -> GameResult<File> {
        self.vfs
            .open_options(path.as_ref(), options)
            .map(|f| File::VfsFile(f))
            .map_err(|e| {
                GameError::ResourceLoadError(format!(
                    "Tried to open {:?} but got error: {:?}",
                    path.as_ref(),
                    e
                ))
            })
    }

    /// Creates a new file in the user directory and opens it
    /// to be written to, truncating it if it already exists.
    pub fn create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
    }

    /// Create an empty directory in the user dir
    /// with the given name.  Any parents to that directory
    /// that do not exist will be created.
    pub fn create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.mkdir(path.as_ref())
    }

    /// Deletes the specified file in the user dir.
    pub fn delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rm(path.as_ref())
    }

    /// Deletes the specified directory in the user dir,
    /// and all its contents!
    pub fn delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rmrf(path.as_ref())
    }

    /// Check whether a file or directory exists.
    pub fn exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs.exists(path.as_ref())
    }

    /// Check whether a path points at a file.
    pub fn is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_file())
            .unwrap_or(false)
    }

    /// Check whether a path points at a directory.
    pub fn is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_dir())
            .unwrap_or(false)
    }

    /// Return the full path to the user data directory
    pub fn get_user_data_dir(&self) -> &path::Path {
        &self.user_data_path
    }

    /// Returns the full path to the resource directory
    /// (even if it doesn't exist)
    pub fn get_resources_dir(&self) -> &path::Path {
        &self.resources_path
    }

    /// Returns a list of all files and directories in the resource directory,
    /// in no particular order.
    ///
    /// Lists the base directory if an empty path is given.
    pub fn read_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<Vec<path::PathBuf>> {
        // TODO: This should return an iterator, and be called iter()
        let itr = self.vfs
            .read_dir(path.as_ref())?
            .map(|fname| fname.unwrap())
            .collect();
        Ok(itr)
    }

    /// Prints the contents of all data directories.
    /// Useful for debugging.
    pub fn print_all(&mut self) -> GameResult<()> {
        for vfs in self.vfs.roots() {
            println!("Source {:?}", vfs);
            for itm in vfs.read_dir(path::Path::new("/"))? {
                println!("  {:?}", itm);
            }
        }
        Ok(())
    }

    /// Looks for a file named "conf.toml" in the resources directory
    /// loads it if it finds it.
    /// If it can't read it for some reason, returns an error.
    pub fn read_config(&mut self) -> GameResult<conf::Conf> {
        let conf_path = path::Path::new(CONFIG_NAME);
        if self.is_file(conf_path) {
            let mut file = self.open(conf_path)?;
            let c = conf::Conf::from_toml_file(&mut file)?;
            Ok(c)
        } else {
            Err(GameError::ConfigError(
                String::from("Config file not found"),
            ))
        }
    }

    /// Takes a `conf::Conf` object and saves it to the user directory,
    /// overwriting any file already there.
    pub fn write_config(&mut self, conf: &conf::Conf) -> GameResult<()> {
        let conf_path = path::Path::new(CONFIG_NAME);
        let mut file = self.create(conf_path)?;
        conf.to_toml_file(&mut file)?;
        if self.is_file(conf_path) {
            Ok(())
        } else {
            Err(GameError::ConfigError(format!(
                "Failed to write config file at {}",
                conf_path.to_string_lossy()
            )))
        }
    }
}

#[cfg(test)]
mod tests {
    use error::*;
    use filesystem::*;
    use std::path;
    use std::io::{Read, Write};
    use conf;

    fn get_dummy_fs_for_tests() -> Filesystem {
        let mut path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("resources");
        let physfs = vfs::PhysicalFS::new(&path, false);
        let mut ofs = vfs::OverlayFS::new();
        ofs.push_front(Box::new(physfs));
        Filesystem {
            vfs: ofs,

            resources_path: "".into(),
            zip_path: "".into(),
            // user_config_path: "".into(),
            user_data_path: "".into(),
        }

    }

    #[test]
    fn test_file_exists() {
        let f = get_dummy_fs_for_tests();

        let tile_file = path::Path::new("/tile.png");
        assert!(f.exists(tile_file));
        assert!(f.is_file(tile_file));

        let tile_file = path::Path::new("/oglebog.png");
        assert!(!f.exists(tile_file));
        assert!(!f.is_file(tile_file));
        assert!(!f.is_dir(tile_file));

    }

    #[test]
    fn test_read_dir() {
        let mut f = get_dummy_fs_for_tests();

        let dir_contents_size = f.read_dir("/").unwrap().len();
        assert!(dir_contents_size > 0);
    }

    #[test]
    fn test_create_delete_file() {
        let mut fs = get_dummy_fs_for_tests();
        let test_file = path::Path::new("/testfile.txt");
        let bytes = "test".as_bytes();

        {
            let mut file = fs.create(test_file).unwrap();
            file.write(bytes).unwrap();
        }
        {
            let mut buffer = Vec::new();
            let mut file = fs.open(test_file).unwrap();
            file.read_to_end(&mut buffer).unwrap();
            assert_eq!(bytes, buffer.as_slice());
        }

        fs.delete(test_file).unwrap();
    }

    #[test]
    fn test_file_not_found() {
        let mut fs = get_dummy_fs_for_tests();
        {
            let rel_file = "testfile.txt";
            match fs.open(rel_file) {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening file with relative path: {:?}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}!", f),
            }
        }

        {
            // This absolute path should work on Windows too since we
            // completely remove filesystem roots.
            match fs.open("/ooglebooglebarg.txt") {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening nonexistent file: {}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}", f),
            }
        }
    }

    #[test]
    fn test_write_config() {
        let mut f = get_dummy_fs_for_tests();
        let conf = conf::Conf::new();
        // The config file should end up in
        // the resources directory with this
        match f.write_config(&conf) {
            Ok(_) => (),
            Err(e) => panic!("{:?}", e),
        }
        // Remove the config file!
        f.delete(CONFIG_NAME).unwrap();
    }
}
<commit_msg>Made it compile.<commit_after>//! Provides a portable interface to the filesystem.
//!
//! This module provides access to files in specific places:
//!
//! * The `resources/` subdirectory in the same directory as the
//! program executable,
//! * The `resources.zip` file in the same
//! directory as the program executable,
//! * The root folder of the  game's "save" directory which is in a
//! platform-dependent location,
//! such as `~/.local/share/<gameid>/` on Linux.  The `gameid`
//! is the the string passed to
//! `Context::load_from_conf()`; some platforms such as Windows also
//! incorporate the `author` string into the path.
//!
//! Files will be looked for in these locations in order, and the first one
//! found used.  That allows game assets to be easily distributed as an archive
//! file, but locally overridden for testing or modding simply by putting
//! altered copies of them in the game's `resources/` directory.
//!
//! Note that the file lookups WILL follow symlinks!  It is
//! more for convenience than absolute security, so don't treat it as
//! being secure.
//!
//! If you build `ggez` with the `cargo-resource-root` feature flag, it will
//! also look for a `resources/` subdirectory in the same directory as your
//! `Cargo.toml`, which can be very convenient for development.

// BUGGO: TODO: Also make it print out the searched directories when it
// can't find a file!

use std::env;
use std::fmt;
use std::io;
use std::path;

use app_dirs::*;

use GameError;
use GameResult;
use conf;
use vfs::{self, VFS};

pub use vfs::OpenOptions;

const CONFIG_NAME: &'static str = "/conf.toml";

/// A structure that contains the filesystem state and cache.
#[derive(Debug)]
pub struct Filesystem {
    vfs: vfs::OverlayFS,
    resources_path: path::PathBuf,
    zip_path: path::PathBuf,
    user_config_path: path::PathBuf,
    user_data_path: path::PathBuf,
}

/// Represents a file, either in the filesystem, or in the resources zip file,
/// or whatever.
pub enum File {
    VfsFile(Box<vfs::VFile>),
}

impl fmt::Debug for File {
    // TODO: Make this more useful.
    // But we can't seem to get a filename out of a file,
    // soooooo.
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            File::VfsFile(ref _file) => write!(f, "VfsFile"),
        }
    }
}

impl io::Read for File {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.read(buf),
        }
    }
}

impl io::Write for File {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match *self {
            File::VfsFile(ref mut f) => f.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match *self {
            File::VfsFile(ref mut f) => f.flush(),
        }
    }
}

impl Filesystem {
    /// Create a new Filesystem instance, using the given `id` and (on
    /// some platforms) the `author` as a portion of the user
    /// directory path.  This function is called automatically by
    /// ggez, the end user should never need to call it.
    pub fn new(id: &'static str, author: &'static str) -> GameResult<Filesystem> {
        let app_info = AppInfo {
            name: id,
            author: author,
        };
        let mut root_path = env::current_exe()?;

        // Ditch the filename (if any)
        if root_path.file_name().is_some() {
            root_path.pop();
        }

        // Set up VFS to merge resource path, root path, and zip path.
        let mut overlay = vfs::OverlayFS::new();

        let mut resources_path;
        let mut resources_zip_path;
        let user_data_path;
        let user_config_path;
        // <game exe root>/resources/
        {
            resources_path = root_path.clone();
            resources_path.push("resources");
            let physfs = vfs::PhysicalFS::new(&resources_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // <root>/resources.zip
        {
            resources_zip_path = root_path.clone();
            resources_zip_path.push("resources.zip");
            if resources_zip_path.exists() {
                let zipfs = vfs::ZipFS::new(&resources_zip_path)?;
                overlay.push_back(Box::new(zipfs));
            }
        }

        // Per-user data dir,
        // ~/.local/share/whatever/
        {
            user_data_path = app_root(AppDataType::UserData, &app_info)?;
            let physfs = vfs::PhysicalFS::new(&user_data_path, true);
            overlay.push_back(Box::new(physfs));
        }

        // Writeable local dir, ~/.config/whatever/
        // Save game dir is read-write
        {
            user_config_path = app_root(AppDataType::UserConfig, &app_info)?;
            let physfs = vfs::PhysicalFS::new(&user_config_path, false);
            overlay.push_back(Box::new(physfs));
        }

        // Cargo manifest dir!
        #[cfg(feature = "cargo-resource-root")]
        {
            if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
                let mut path = path::PathBuf::from(manifest_dir);
                path.push("resources");
                let physfs = vfs::PhysicalFS::new(&path, true);
                overlay.push_back(Box::new(physfs));
            }
        }

        let fs = Filesystem {
            vfs: overlay,
            resources_path: resources_path,
            zip_path: resources_zip_path,
            user_config_path: user_config_path,
            user_data_path: user_data_path,
        };

        Ok(fs)
    }

    /// Opens the given path and returns the resulting `File`
    /// in read-only mode.
    pub fn open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs.open(path.as_ref()).map(|f| File::VfsFile(f))
    }

    /// Opens a file in the user directory with the given `filesystem::OpenOptions`.
    /// Note that even if you open a file read-only, it can only access
    /// files in the user directory.
    pub fn open_options<P: AsRef<path::Path>>(
        &mut self,
        path: P,
        options: &OpenOptions,
    ) -> GameResult<File> {
        self.vfs
            .open_options(path.as_ref(), options)
            .map(|f| File::VfsFile(f))
            .map_err(|e| {
                GameError::ResourceLoadError(format!(
                    "Tried to open {:?} but got error: {:?}",
                    path.as_ref(),
                    e
                ))
            })
    }

    /// Creates a new file in the user directory and opens it
    /// to be written to, truncating it if it already exists.
    pub fn create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
        self.vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
    }

    /// Create an empty directory in the user dir
    /// with the given name.  Any parents to that directory
    /// that do not exist will be created.
    pub fn create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.mkdir(path.as_ref())
    }

    /// Deletes the specified file in the user dir.
    pub fn delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rm(path.as_ref())
    }

    /// Deletes the specified directory in the user dir,
    /// and all its contents!
    pub fn delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
        self.vfs.rmrf(path.as_ref())
    }

    /// Check whether a file or directory exists.
    pub fn exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs.exists(path.as_ref())
    }

    /// Check whether a path points at a file.
    pub fn is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_file())
            .unwrap_or(false)
    }

    /// Check whether a path points at a directory.
    pub fn is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
        self.vfs
            .metadata(path.as_ref())
            .map(|m| m.is_dir())
            .unwrap_or(false)
    }

    /// Return the full path to the user data directory
    pub fn get_user_data_dir(&self) -> &path::Path {
        &self.user_data_path
    }

    /// Returns the full path to the resource directory
    /// (even if it doesn't exist)
    pub fn get_resources_dir(&self) -> &path::Path {
        &self.resources_path
    }

    /// Returns a list of all files and directories in the resource directory,
    /// in no particular order.
    ///
    /// Lists the base directory if an empty path is given.
    pub fn read_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<Vec<path::PathBuf>> {
        // TODO: This should return an iterator, and be called iter()
        let itr = self.vfs
            .read_dir(path.as_ref())?
            .map(|fname| fname.unwrap())
            .collect();
        Ok(itr)
    }

    /// Prints the contents of all data directories.
    /// Useful for debugging.
    pub fn print_all(&mut self) -> GameResult<()> {
        for vfs in self.vfs.roots() {
            println!("Source {:?}", vfs);
            for itm in vfs.read_dir(path::Path::new("/"))? {
                println!("  {:?}", itm);
            }
        }
        Ok(())
    }

    /// Looks for a file named "conf.toml" in the resources directory
    /// loads it if it finds it.
    /// If it can't read it for some reason, returns an error.
    pub fn read_config(&mut self) -> GameResult<conf::Conf> {
        let conf_path = path::Path::new(CONFIG_NAME);
        if self.is_file(conf_path) {
            let mut file = self.open(conf_path)?;
            let c = conf::Conf::from_toml_file(&mut file)?;
            Ok(c)
        } else {
            Err(GameError::ConfigError(
                String::from("Config file not found"),
            ))
        }
    }

    /// Takes a `conf::Conf` object and saves it to the user directory,
    /// overwriting any file already there.
    pub fn write_config(&mut self, conf: &conf::Conf) -> GameResult<()> {
        let conf_path = path::Path::new(CONFIG_NAME);
        let mut file = self.create(conf_path)?;
        conf.to_toml_file(&mut file)?;
        if self.is_file(conf_path) {
            Ok(())
        } else {
            Err(GameError::ConfigError(format!(
                "Failed to write config file at {}",
                conf_path.to_string_lossy()
            )))
        }
    }
}

#[cfg(test)]
mod tests {
    use error::*;
    use filesystem::*;
    use std::path;
    use std::io::{Read, Write};
    use conf;

    fn get_dummy_fs_for_tests() -> Filesystem {
        let mut path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("resources");
        let physfs = vfs::PhysicalFS::new(&path, false);
        let mut ofs = vfs::OverlayFS::new();
        ofs.push_front(Box::new(physfs));
        Filesystem {
            vfs: ofs,

            resources_path: "".into(),
            zip_path: "".into(),
            user_config_path: "".into(),
            user_data_path: "".into(),
        }

    }

    #[test]
    fn test_file_exists() {
        let f = get_dummy_fs_for_tests();

        let tile_file = path::Path::new("/tile.png");
        assert!(f.exists(tile_file));
        assert!(f.is_file(tile_file));

        let tile_file = path::Path::new("/oglebog.png");
        assert!(!f.exists(tile_file));
        assert!(!f.is_file(tile_file));
        assert!(!f.is_dir(tile_file));

    }

    #[test]
    fn test_read_dir() {
        let mut f = get_dummy_fs_for_tests();

        let dir_contents_size = f.read_dir("/").unwrap().len();
        assert!(dir_contents_size > 0);
    }

    #[test]
    fn test_create_delete_file() {
        let mut fs = get_dummy_fs_for_tests();
        let test_file = path::Path::new("/testfile.txt");
        let bytes = "test".as_bytes();

        {
            let mut file = fs.create(test_file).unwrap();
            file.write(bytes).unwrap();
        }
        {
            let mut buffer = Vec::new();
            let mut file = fs.open(test_file).unwrap();
            file.read_to_end(&mut buffer).unwrap();
            assert_eq!(bytes, buffer.as_slice());
        }

        fs.delete(test_file).unwrap();
    }

    #[test]
    fn test_file_not_found() {
        let mut fs = get_dummy_fs_for_tests();
        {
            let rel_file = "testfile.txt";
            match fs.open(rel_file) {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening file with relative path: {:?}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}!", f),
            }
        }

        {
            // This absolute path should work on Windows too since we
            // completely remove filesystem roots.
            match fs.open("/ooglebooglebarg.txt") {
                Err(GameError::ResourceNotFound(_, _)) => (),
                Err(e) => panic!("Invalid error for opening nonexistent file: {}", e),
                Ok(f) => panic!("Should have gotten an error but instead got {:?}", f),
            }
        }
    }

    #[test]
    fn test_write_config() {
        let mut f = get_dummy_fs_for_tests();
        let conf = conf::Conf::new();
        // The config file should end up in
        // the resources directory with this
        match f.write_config(&conf) {
            Ok(_) => (),
            Err(e) => panic!("{:?}", e),
        }
        // Remove the config file!
        f.delete(CONFIG_NAME).unwrap();
    }
}
<|endoftext|>


<commit_before>// Zinc, the bare metal stack for rust.
// Copyright 2014 Ben Gamari <bgamari@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Tests for ioreg! syntax extension

#![feature(phase)]
#[phase(plugin)] extern crate ioreg;
#[phase(plugin,link)] extern crate shiny;
extern crate core;

#[path="../zinc/util/volatile_cell.rs"] mod volatile_cell;

#[cfg(test)]
mod test {
  use std::mem::{transmute, zeroed};
  use std::ptr::PtrExt;
  use volatile_cell::VolatileCell;

  fn get_value<'a, T>(v: &'a T, offset: uint) -> u32 {
    unsafe {
      let ptr: *const u32 = transmute(v);
      *(ptr.offset(offset as int))
    }
  }

  fn zeroed_safe<T: Copy>() -> T {
    unsafe {
      return zeroed();
    }
  }

  ioregs!(BASIC_TEST = {
    0x0 => reg32 reg1 {
      0      => field1,
      1..3   => field2,
      16..24 => field3,
      25     => field4: set_to_clear,
    }
    0x4 => reg32 reg2 {
      0      => field1,
    }
    0x8 => reg32 wo_reg {
      0..15  => field1: wo,
      16..31 => field2: wo,
    }
  });

  describe!(
    before_each {
      let test: BASIC_TEST = zeroed_safe();
    }

    it "can round_trip simple field values 1" {
      test.reg1.set_field1(true);
      assert_eq!(test.reg1.field1(), true);
      assert_eq!(get_value(&test, 0), 1);
      assert_eq!(get_value(&test, 1), 0);
    }

    it "can round trip simple field values 2" {
      test.reg1.set_field3(0xde);
      assert_eq!(test.reg1.field3(), 0xde);
      assert_eq!(get_value(&test, 0), 0xde<<16);
    }

    it "sets set_to_clear fields" {
      test.reg1.clear_field4();
      assert_eq!(get_value(&test, 0), 1<<25);
    }

    it "does not read from writeonly registers" {
      test.wo_reg.set_field1(0xdead);
      assert_eq!(get_value(&test, 2), 0xdead);
      test.wo_reg.set_field2(0xdead);
      assert_eq!(get_value(&test, 2), 0xdead<<16);
    }
  );

  ioregs!(GROUP_TEST = {
    0x0 => group regs[5] {
      0x0 => reg32 reg1 {
        0..31 => field1
      }
      0x4 => reg32 reg2 {
        0..31 => field2
      }
    }
  });

  describe!(
    before_each {
      let test: GROUP_TEST = zeroed_safe();
    }

    it "sets groups correctly" {
      test.regs[0].reg1.set_field1(0xdeadbeef);
      assert_eq!(test.regs[0].reg1.field1(), 0xdeadbeef);
      assert_eq!(get_value(&test, 0), 0xdeadbeef);
      for i in range(1, 10) {
        assert_eq!(get_value(&test, i), 0);
      }

      test.regs[2].reg2.set_field2(0xfeedbeef);
      assert_eq!(test.regs[2].reg2.field2(), 0xfeedbeef);
      assert_eq!(get_value(&test, 5), 0xfeedbeef);
    }
  );

  ioregs!(FIELD_ARRAY_TEST = {
    0x0 => reg32 reg1 {
      0..31 => field[16]
    }
  });

  describe!(
    before_each {
      let test: FIELD_ARRAY_TEST = zeroed_safe();
    }

    it "sets field arrays correctly" {
      test.reg1.set_field(0, 1);
      assert_eq!(test.reg1.field(0), 1);
      assert_eq!(get_value(&test, 0), 0x1);

      test.reg1.set_field(4, 3);
      assert_eq!(test.reg1.field(4), 3);
      assert_eq!(get_value(&test, 0), 0x1 | 0x3<<8);
    }
  );

  ioregs!(GAP_TEST = {
    0x0 => reg32 reg1 {
      0..31 => field,
    }
    0x10 => reg32 reg2 {
      0..31 => field,
    }
    0x14 => reg32 reg3 {
      0..31 => field,
    }
    0x20 => reg32 reg4 {
      0..31 => field,
    }
  });

  describe!(
    before_each {
      let test: GAP_TEST = zeroed_safe();
      let base = &test as *const GAP_TEST;
    }
    it "has zero base offset" {
      let addr = &test.reg1 as *const GAP_TEST_reg1;
      assert_eq!(addr.to_uint() - base.to_uint(), 0x0);
    }
    it "computes the correct first gap" {
      let addr = &test.reg2 as *const GAP_TEST_reg2;
      assert_eq!(addr.to_uint() - base.to_uint(), 0x10);
    }
    it "computes the correct second gap" {
      let addr = &test.reg4 as *const GAP_TEST_reg4;
      assert_eq!(addr.to_uint() - base.to_uint(), 0x20);
    }
  );
}
<commit_msg>to_uint() to as uint<commit_after>// Zinc, the bare metal stack for rust.
// Copyright 2014 Ben Gamari <bgamari@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Tests for ioreg! syntax extension

#![feature(phase)]
#[phase(plugin)] extern crate ioreg;
#[phase(plugin,link)] extern crate shiny;
extern crate core;

#[path="../zinc/util/volatile_cell.rs"] mod volatile_cell;

#[cfg(test)]
mod test {
  use std::mem::{transmute, zeroed};
  use std::ptr::PtrExt;
  use volatile_cell::VolatileCell;

  fn get_value<'a, T>(v: &'a T, offset: uint) -> u32 {
    unsafe {
      let ptr: *const u32 = transmute(v);
      *(ptr.offset(offset as int))
    }
  }

  fn zeroed_safe<T: Copy>() -> T {
    unsafe {
      return zeroed();
    }
  }

  ioregs!(BASIC_TEST = {
    0x0 => reg32 reg1 {
      0      => field1,
      1..3   => field2,
      16..24 => field3,
      25     => field4: set_to_clear,
    }
    0x4 => reg32 reg2 {
      0      => field1,
    }
    0x8 => reg32 wo_reg {
      0..15  => field1: wo,
      16..31 => field2: wo,
    }
  });

  describe!(
    before_each {
      let test: BASIC_TEST = zeroed_safe();
    }

    it "can round_trip simple field values 1" {
      test.reg1.set_field1(true);
      assert_eq!(test.reg1.field1(), true);
      assert_eq!(get_value(&test, 0), 1);
      assert_eq!(get_value(&test, 1), 0);
    }

    it "can round trip simple field values 2" {
      test.reg1.set_field3(0xde);
      assert_eq!(test.reg1.field3(), 0xde);
      assert_eq!(get_value(&test, 0), 0xde<<16);
    }

    it "sets set_to_clear fields" {
      test.reg1.clear_field4();
      assert_eq!(get_value(&test, 0), 1<<25);
    }

    it "does not read from writeonly registers" {
      test.wo_reg.set_field1(0xdead);
      assert_eq!(get_value(&test, 2), 0xdead);
      test.wo_reg.set_field2(0xdead);
      assert_eq!(get_value(&test, 2), 0xdead<<16);
    }
  );

  ioregs!(GROUP_TEST = {
    0x0 => group regs[5] {
      0x0 => reg32 reg1 {
        0..31 => field1
      }
      0x4 => reg32 reg2 {
        0..31 => field2
      }
    }
  });

  describe!(
    before_each {
      let test: GROUP_TEST = zeroed_safe();
    }

    it "sets groups correctly" {
      test.regs[0].reg1.set_field1(0xdeadbeef);
      assert_eq!(test.regs[0].reg1.field1(), 0xdeadbeef);
      assert_eq!(get_value(&test, 0), 0xdeadbeef);
      for i in range(1, 10) {
        assert_eq!(get_value(&test, i), 0);
      }

      test.regs[2].reg2.set_field2(0xfeedbeef);
      assert_eq!(test.regs[2].reg2.field2(), 0xfeedbeef);
      assert_eq!(get_value(&test, 5), 0xfeedbeef);
    }
  );

  ioregs!(FIELD_ARRAY_TEST = {
    0x0 => reg32 reg1 {
      0..31 => field[16]
    }
  });

  describe!(
    before_each {
      let test: FIELD_ARRAY_TEST = zeroed_safe();
    }

    it "sets field arrays correctly" {
      test.reg1.set_field(0, 1);
      assert_eq!(test.reg1.field(0), 1);
      assert_eq!(get_value(&test, 0), 0x1);

      test.reg1.set_field(4, 3);
      assert_eq!(test.reg1.field(4), 3);
      assert_eq!(get_value(&test, 0), 0x1 | 0x3<<8);
    }
  );

  ioregs!(GAP_TEST = {
    0x0 => reg32 reg1 {
      0..31 => field,
    }
    0x10 => reg32 reg2 {
      0..31 => field,
    }
    0x14 => reg32 reg3 {
      0..31 => field,
    }
    0x20 => reg32 reg4 {
      0..31 => field,
    }
  });

  describe!(
    before_each {
      let test: GAP_TEST = zeroed_safe();
      let base = &test as *const GAP_TEST;
    }
    it "has zero base offset" {
      let addr = &test.reg1 as *const GAP_TEST_reg1;
      assert_eq!(addr as uint - base as uint, 0x0);
    }
    it "computes the correct first gap" {
      let addr = &test.reg2 as *const GAP_TEST_reg2;
      assert_eq!(addr as uint - base as uint, 0x10);
    }
    it "computes the correct second gap" {
      let addr = &test.reg4 as *const GAP_TEST_reg4;
      assert_eq!(addr as uint - base as uint, 0x20);
    }
  );
}
<|endoftext|>


<commit_before>pub mod files;

use std::cmp;
use std::iter;
use std::io::Write;

const MAX_PATCH_LEN: usize = 0xFFFF;

#[derive(Debug)]
pub struct Ips {
    buffer: Vec<u8>
}

impl Ips {
    pub fn new(buffer: &Vec<u8>) -> Self {
        Ips {
            buffer: buffer.clone()
        }
    }

    pub fn diff(&self, change: &Vec<u8>) -> Vec<Patch> {
        let (change_within, additional) = change.split_at(self.buffer.len());
        let mut pairs = (0..).zip(self.buffer.iter().zip(change_within));

        let mut patches: Vec<Patch> = vec![];
        let mut index = 0;
        let mut data = vec![];

        while let Some((offset, (&before, &after))) = pairs.next() {
            if before != after {
                if data.is_empty() {
                    index = offset;
                }
                data.push(after);
            } else if ! data.is_empty() {
                let patch = Patch::new(index, data);
                patches.push(patch);
                data = vec![];
            }
        };

        for chunk in additional.chunks(MAX_PATCH_LEN) {
            let patch = Patch::new(self.buffer.len() as u32, chunk.to_vec());
            patches.push(patch);
        }

        patches
    }

    pub fn patch(&self, change: &Vec<Patch>) -> Vec<u8> {
        let total_size = cmp::max(self.buffer.len(), patch_max_len(&change).unwrap());

        let mut output = Vec::with_capacity(total_size);
        output.write(&self.buffer);

        for patch in change {
            {
                let (_, mut skipped) = output.split_at_mut(patch.addr as usize);
                skipped.write(&patch.data())
            }.map(|written_len| {
                let data = patch.data();
                let (_, unwritten_data) = data.split_at(written_len);
                for &b in unwritten_data {
                    output.push(b);
                }
            });
        }
        output
    }

}

pub fn patch_max_len(patch: &[Patch]) -> Option<usize> {
    patch.iter().map(|x| x.addr as usize + x.data().len()).max()
}

pub fn serialize_patches(patches: Vec<Patch>) -> Vec<u8> {
    let patch_contents: Vec<u8> = patches.iter().flat_map(|p| p.bytes()).collect();
    let mut binary: Vec<u8> = b"PATCH".to_vec();
    binary.extend(patch_contents);
    binary.extend(b"EOF".to_vec());
    binary
}

pub fn unserialize_patches(binary: Vec<u8>) -> Option<Vec<Patch>> {
    let mut patches: Vec<Patch> = Vec::new();

    let slice = &binary;
    let (header, mut slice) = slice.split_at(5);

    if header != b"PATCH" {
        return None;
    }

    loop {
        // TODO support RLE'd patches
        let (addr_slice, rest) = slice.split_at(3);
        let (len_slice, rest) = rest.split_at(2);

        if (! addr_slice.is_empty()) && len_slice.len() < 2 {
            // Malformed data
            return None;
        }

        let mut addr_array = [0; 3];
        addr_array.clone_from_slice(addr_slice);
        let addr = Patch::unserialize_addr_array(addr_array);

        let mut len_array = [0; 2];
        len_array.clone_from_slice(len_slice);
        let len = Patch::unserialize_len(len_array);

        if rest.len() < len {
            // Malformed data
            return None;
        }

        let (patch, rest) = if len == 0 {
            // should decode RLE
            let (rle_len_slice, rest) = rest.split_at(2);

            let mut rle_len_array = [0; 2];
            rle_len_array.clone_from_slice(rle_len_slice);
            let rle_len = Patch::unserialize_len(rle_len_array);

            let (rle_val_slice, rest) = rest.split_at(1);
            let rle_val = rle_val_slice[0];

            let patch = Patch::new(addr, from_rle(rle_len, rle_val));
            (patch, rest)
        } else {
            let (data, rest) = rest.split_at(len);

            (Patch::new(addr, data.to_vec()), rest)
        };

        patches.push(patch);

        slice = rest;

        let (possible_eof, _) = rest.split_at(3);
        if possible_eof == b"EOF" {
            // We are done here
            break;
        }
    }

    Some(patches)
}

pub fn into_rle(data: Vec<u8>) -> (usize, u8) {
    (data.len(), data[0])
}

pub fn from_rle(len: usize, val: u8) -> Vec<u8> {
    iter::repeat(val).take(len).collect()
}

#[derive(Debug, PartialEq, Clone)]
pub struct Patch {
    addr: u32,
    data: Vec<u8>
}

impl Patch {
    pub fn new(addr: u32, data: Vec<u8>) -> Self {
        assert!(addr <= 0xFFFFFF);
        Patch {
            addr: addr,
            data: data
        }
    }

    pub fn data(&self) -> &Vec<u8> {
        &self.data
    }

    pub fn bytes(&self) -> Vec<u8> {
        let mut bytes: Vec<u8> = self.serialize_addr();
        let len = self.serialize_len();
        bytes.extend(len);
        bytes.extend(self.data());
        bytes
    }

    pub fn unserialize_addr_array(addr: [u8; 3]) -> u32 {
        ((addr[0] as u32) << 16) | ((addr[1] as u32) << 8) | addr[2] as u32
    }

    pub fn serialize_addr(&self) -> Vec<u8> {
        vec![(self.addr >> 16) as u8, (self.addr >> 8) as u8, self.addr as u8]
    }

    pub fn unserialize_len(addr: [u8; 2]) -> usize {
        (((addr[0] as u32) << 8) | addr[1] as u32) as usize
    }

    pub fn serialize_len(&self) -> Vec<u8> {
        vec![(self.data().len() >> 8) as u8, self.data().len() as u8]
    }
}
<commit_msg>Fail on missing/malformed EOF<commit_after>pub mod files;

use std::cmp;
use std::iter;
use std::io::Write;

const MAX_PATCH_LEN: usize = 0xFFFF;

#[derive(Debug)]
pub struct Ips {
    buffer: Vec<u8>
}

impl Ips {
    pub fn new(buffer: &Vec<u8>) -> Self {
        Ips {
            buffer: buffer.clone()
        }
    }

    pub fn diff(&self, change: &Vec<u8>) -> Vec<Patch> {
        let (change_within, additional) = change.split_at(self.buffer.len());
        let mut pairs = (0..).zip(self.buffer.iter().zip(change_within));

        let mut patches: Vec<Patch> = vec![];
        let mut index = 0;
        let mut data = vec![];

        while let Some((offset, (&before, &after))) = pairs.next() {
            if before != after {
                if data.is_empty() {
                    index = offset;
                }
                data.push(after);
            } else if ! data.is_empty() {
                let patch = Patch::new(index, data);
                patches.push(patch);
                data = vec![];
            }
        };

        for chunk in additional.chunks(MAX_PATCH_LEN) {
            let patch = Patch::new(self.buffer.len() as u32, chunk.to_vec());
            patches.push(patch);
        }

        patches
    }

    pub fn patch(&self, change: &Vec<Patch>) -> Vec<u8> {
        let total_size = cmp::max(self.buffer.len(), patch_max_len(&change).unwrap());

        let mut output = Vec::with_capacity(total_size);
        output.write(&self.buffer);

        for patch in change {
            {
                let (_, mut skipped) = output.split_at_mut(patch.addr as usize);
                skipped.write(&patch.data())
            }.map(|written_len| {
                let data = patch.data();
                let (_, unwritten_data) = data.split_at(written_len);
                for &b in unwritten_data {
                    output.push(b);
                }
            });
        }
        output
    }

}

pub fn patch_max_len(patch: &[Patch]) -> Option<usize> {
    patch.iter().map(|x| x.addr as usize + x.data().len()).max()
}

pub fn serialize_patches(patches: Vec<Patch>) -> Vec<u8> {
    let patch_contents: Vec<u8> = patches.iter().flat_map(|p| p.bytes()).collect();
    let mut binary: Vec<u8> = b"PATCH".to_vec();
    binary.extend(patch_contents);
    binary.extend(b"EOF".to_vec());
    binary
}

pub fn unserialize_patches(binary: Vec<u8>) -> Option<Vec<Patch>> {
    let mut patches: Vec<Patch> = Vec::new();

    let slice = &binary;
    let (header, mut slice) = slice.split_at(5);

    if header != b"PATCH" {
        return None;
    }

    loop {
        // TODO support RLE'd patches
        let (addr_slice, rest) = slice.split_at(3);
        let (len_slice, rest) = rest.split_at(2);

        if (! addr_slice.is_empty()) && len_slice.len() < 2 {
            // Malformed data
            return None;
        }

        let mut addr_array = [0; 3];
        addr_array.clone_from_slice(addr_slice);
        let addr = Patch::unserialize_addr_array(addr_array);

        let mut len_array = [0; 2];
        len_array.clone_from_slice(len_slice);
        let len = Patch::unserialize_len(len_array);

        if rest.len() < len {
            // Malformed data
            return None;
        }

        let (patch, rest) = if len == 0 {
            // should decode RLE
            let (rle_len_slice, rest) = rest.split_at(2);

            let mut rle_len_array = [0; 2];
            rle_len_array.clone_from_slice(rle_len_slice);
            let rle_len = Patch::unserialize_len(rle_len_array);

            let (rle_val_slice, rest) = rest.split_at(1);
            let rle_val = rle_val_slice[0];

            let patch = Patch::new(addr, from_rle(rle_len, rle_val));
            (patch, rest)
        } else {
            let (data, rest) = rest.split_at(len);

            (Patch::new(addr, data.to_vec()), rest)
        };

        patches.push(patch);

        if rest.len() < 3 {
            // Malformed data
            return None;
        }

        let (possible_eof, _) = rest.split_at(3);
        if possible_eof == b"EOF" {
            // We are done here
            break;
        }

        slice = rest;
    }

    Some(patches)
}

pub fn into_rle(data: Vec<u8>) -> (usize, u8) {
    (data.len(), data[0])
}

pub fn from_rle(len: usize, val: u8) -> Vec<u8> {
    iter::repeat(val).take(len).collect()
}

#[derive(Debug, PartialEq, Clone)]
pub struct Patch {
    addr: u32,
    data: Vec<u8>
}

impl Patch {
    pub fn new(addr: u32, data: Vec<u8>) -> Self {
        assert!(addr <= 0xFFFFFF);
        Patch {
            addr: addr,
            data: data
        }
    }

    pub fn data(&self) -> &Vec<u8> {
        &self.data
    }

    pub fn bytes(&self) -> Vec<u8> {
        let mut bytes: Vec<u8> = self.serialize_addr();
        let len = self.serialize_len();
        bytes.extend(len);
        bytes.extend(self.data());
        bytes
    }

    pub fn unserialize_addr_array(addr: [u8; 3]) -> u32 {
        ((addr[0] as u32) << 16) | ((addr[1] as u32) << 8) | addr[2] as u32
    }

    pub fn serialize_addr(&self) -> Vec<u8> {
        vec![(self.addr >> 16) as u8, (self.addr >> 8) as u8, self.addr as u8]
    }

    pub fn unserialize_len(addr: [u8; 2]) -> usize {
        (((addr[0] as u32) << 8) | addr[1] as u32) as usize
    }

    pub fn serialize_len(&self) -> Vec<u8> {
        vec![(self.data().len() >> 8) as u8, self.data().len() as u8]
    }
}
<|endoftext|>


<commit_before>use std::collections::BinaryHeap;
use std::cmp::Ord;

pub(crate) fn k_smallest<T: Ord, I: Iterator<Item = T>>(mut iter: I, k: usize) -> BinaryHeap<T> {
    if k == 0 { return BinaryHeap::new(); }

    let mut heap = iter.by_ref().take(k).collect::<BinaryHeap<_>>();

    for i in iter {
        // Guaranteed not-None, since we keep exactly k>0 elements in the heap.
        let mut lorgest = heap.peek_mut().unwrap();
        if *lorgest > i { *lorgest = i; }
    }

    heap
}
<commit_msg>k_smollest: Add debug_assert for invariant<commit_after>use std::collections::BinaryHeap;
use std::cmp::Ord;

pub(crate) fn k_smallest<T: Ord, I: Iterator<Item = T>>(mut iter: I, k: usize) -> BinaryHeap<T> {
    if k == 0 { return BinaryHeap::new(); }

    let mut heap = iter.by_ref().take(k).collect::<BinaryHeap<_>>();

    for i in iter {
        debug_assert_eq!(heap.len(), k);
        // Guaranteed not-None, since we keep exactly k>0 elements in the heap.
        let mut lorgest = heap.peek_mut().unwrap();
        if *lorgest > i { *lorgest = i; }
    }

    heap
}
<|endoftext|>


<commit_before>//! # runner
//!
//! Runs the requested tasks.

//! The flow is as follows:
//!
//! * Load env variables
//! * Create an execution plan based on the requested task and its dependencies
//! * Run all tasks defined in the execution plan
//!

#[cfg(test)]
#[path = "./runner_test.rs"]
mod runner_test;

use crate::command;
use crate::condition;
use crate::environment;
use crate::execution_plan::create as create_execution_plan;
use crate::functions;
use crate::installer;
use crate::logger;
use crate::profile;
use crate::scriptengine;
use crate::types::{
    CliArgs, Config, DeprecationInfo, EnvInfo, EnvValue, ExecutionPlan, FlowInfo, RunTaskInfo,
    RunTaskName, RunTaskRoutingInfo, Step, Task, TaskWatchOptions,
};
use indexmap::IndexMap;
use std::env;
use std::thread;
use std::time::SystemTime;

fn do_in_task_working_directory<F>(step: &Step, mut action: F)
where
    F: FnMut(),
{
    let revert_directory = match step.config.cwd {
        Some(ref cwd) => {
            let expanded_cwd = environment::expand_value(cwd);

            if expanded_cwd.len() > 0 {
                let directory = envmnt::get_or("CARGO_MAKE_WORKING_DIRECTORY", "");

                environment::setup_cwd(Some(&expanded_cwd));

                directory
            } else {
                "".to_string()
            }
        }
        None => "".to_string(),
    };

    action();

    // revert to original cwd
    match step.config.cwd {
        Some(_) => environment::setup_cwd(Some(&revert_directory)),
        _ => (),
    };
}

fn validate_condition(flow_info: &FlowInfo, step: &Step) -> bool {
    let mut valid = true;

    let do_validate = || {
        valid = condition::validate_condition_for_step(&flow_info, &step);
    };

    do_in_task_working_directory(&step, do_validate);

    valid
}

pub(crate) fn get_sub_task_info_for_routing_info(
    flow_info: &FlowInfo,
    routing_info: &Vec<RunTaskRoutingInfo>,
) -> (Option<Vec<String>>, bool, bool) {
    let mut task_name = None;

    let mut fork = false;
    let mut parallel = false;
    for routing_step in routing_info {
        let invoke = condition::validate_conditions(
            &flow_info,
            &routing_step.condition,
            &routing_step.condition_script,
            None,
        );

        if invoke {
            let task_name_values = match routing_step.name.clone() {
                RunTaskName::Single(name) => vec![name],
                RunTaskName::Multiple(names) => names,
            };
            task_name = Some(task_name_values);
            fork = routing_step.fork.unwrap_or(false);
            parallel = routing_step.parallel.unwrap_or(false);
            break;
        }
    }

    (task_name, fork, parallel)
}

fn create_fork_step(flow_info: &FlowInfo) -> Step {
    let fork_task = create_proxy_task(&flow_info.task, true, true);

    Step {
        name: "cargo_make_run_fork".to_string(),
        config: fork_task,
    }
}

fn run_forked_task(flow_info: &FlowInfo) {
    // run task as a sub process
    let step = create_fork_step(&flow_info);
    run_task(&flow_info, &step);
}

/// runs a sub task and returns true/false based if a sub task was actually invoked
fn run_sub_task_and_report(flow_info: &FlowInfo, sub_task: &RunTaskInfo) -> bool {
    let (task_names, fork, parallel) = match sub_task {
        RunTaskInfo::Name(ref name) => (Some(vec![name.to_string()]), false, false),
        RunTaskInfo::Details(ref details) => {
            let task_name_values = match details.name.clone() {
                RunTaskName::Single(name) => vec![name],
                RunTaskName::Multiple(names) => names,
            };
            (
                Some(task_name_values),
                details.fork.unwrap_or(false),
                details.parallel.unwrap_or(false),
            )
        }
        RunTaskInfo::Routing(ref routing_info) => {
            get_sub_task_info_for_routing_info(&flow_info, routing_info)
        }
    };

    if task_names.is_some() {
        let names = task_names.unwrap();
        let mut threads = vec![];

        for name in names {
            let task_run_fn = |flow_info: &FlowInfo, fork: bool| {
                let mut sub_flow_info = flow_info.clone();
                sub_flow_info.task = name;

                if fork {
                    run_forked_task(&sub_flow_info);
                } else {
                    run_flow(&sub_flow_info, true);
                }
            };

            if parallel {
                let run_flow_info = flow_info.clone();
                threads.push(thread::spawn(move || {
                    task_run_fn(&run_flow_info, fork);
                }));
            } else {
                task_run_fn(&flow_info, fork);
            }
        }

        if threads.len() > 0 {
            for task_thread in threads {
                task_thread.join().unwrap();
            }
        }

        true
    } else {
        false
    }
}

fn run_sub_task(flow_info: &FlowInfo, sub_task: &RunTaskInfo) {
    run_sub_task_and_report(&flow_info, &sub_task);
}

fn create_watch_task_name(task: &str) -> String {
    let mut watch_task_name = "".to_string();
    watch_task_name.push_str(&task);
    watch_task_name.push_str("-watch");

    watch_task_name
}

fn create_watch_step(task: &str, options: Option<TaskWatchOptions>) -> Step {
    let watch_task = create_watch_task(&task, options);

    let watch_task_name = create_watch_task_name(&task);

    Step {
        name: watch_task_name,
        config: watch_task,
    }
}

fn watch_task(flow_info: &FlowInfo, task: &str, options: Option<TaskWatchOptions>) {
    let step = create_watch_step(&task, options);

    run_task(&flow_info, &step);
}

fn is_watch_enabled() -> bool {
    !envmnt::is_or("CARGO_MAKE_DISABLE_WATCH", false)
}

fn should_watch(task: &Task) -> bool {
    match task.watch {
        Some(ref watch_value) => match watch_value {
            TaskWatchOptions::Boolean(watch_bool) => {
                if *watch_bool {
                    is_watch_enabled()
                } else {
                    false
                }
            }
            TaskWatchOptions::Options(_) => is_watch_enabled(),
        },
        None => false,
    }
}

fn run_task(flow_info: &FlowInfo, step: &Step) {
    match step.config.env {
        Some(ref env) => environment::set_current_task_meta_info_env(env.clone()),
        None => (),
    };

    if validate_condition(&flow_info, &step) {
        info!("Running Task: {}", &step.name);

        if !step.config.is_valid() {
            error!(
                "Invalid task, contains multiple actions.\n{:#?}",
                &step.config
            );
        }

        let deprecated_info = step.config.deprecated.clone();
        match deprecated_info {
            Some(deprecated) => match deprecated {
                DeprecationInfo::Boolean(value) => {
                    if value {
                        warn!("Task: {} is deprecated.", &step.name);
                    }

                    ()
                }
                DeprecationInfo::Message(ref message) => {
                    warn!("Task: {} is deprecated - {}", &step.name, message);

                    ()
                }
            },
            None => (),
        };

        //get profile
        let profile_name = profile::get();

        match step.config.env_files {
            Some(ref env_files) => environment::set_env_files(env_files.clone()),
            None => (),
        };
        match step.config.env {
            Some(ref env) => environment::set_env(env.clone()),
            None => (),
        };

        envmnt::set("CARGO_MAKE_CURRENT_TASK_NAME", &step.name);

        //make sure profile env is not overwritten
        profile::set(&profile_name);

        // modify step using env and functions
        let mut updated_step = functions::run(&step);
        updated_step = environment::expand_env(&updated_step);

        let watch = should_watch(&step.config);

        if watch {
            watch_task(&flow_info, &step.name, step.config.watch.clone());
        } else {
            do_in_task_working_directory(&step, || {
                installer::install(&updated_step.config);
            });

            match step.config.run_task {
                Some(ref sub_task) => run_sub_task(&flow_info, sub_task),
                None => {
                    do_in_task_working_directory(&step, || {
                        // get cli arguments
                        let cli_arguments = match flow_info.cli_arguments {
                            Some(ref args) => args.clone(),
                            None => vec![],
                        };

                        // run script
                        let script_runner_done =
                            scriptengine::invoke(&updated_step.config, &cli_arguments);

                        // run command
                        if !script_runner_done {
                            command::run(&updated_step);
                        };
                    });
                }
            };
        }
    } else {
        let fail_message = match step.config.condition {
            Some(ref condition) => match condition.fail_message {
                Some(ref value) => value.to_string(),
                None => "".to_string(),
            },
            None => "".to_string(),
        };
        info!("Skipping Task: {} {}", &step.name, &fail_message);
    }
}

fn run_task_flow(flow_info: &FlowInfo, execution_plan: &ExecutionPlan) {
    for step in &execution_plan.steps {
        run_task(&flow_info, &step);
    }
}

fn create_watch_task(task: &str, options: Option<TaskWatchOptions>) -> Task {
    let mut task_config = create_proxy_task(&task, true, true);

    let mut env_map = task_config.env.unwrap_or(IndexMap::new());
    env_map.insert(
        "CARGO_MAKE_DISABLE_WATCH".to_string(),
        EnvValue::Value("true".to_string()),
    );
    task_config.env = Some(env_map);

    let make_args = task_config.args.unwrap();
    let make_command = &make_args.join(" ");

    let mut watch_args = vec!["watch".to_string(), "-q".to_string()];

    match options {
        Some(task_watch_options) => match task_watch_options {
            TaskWatchOptions::Options(watch_options) => {
                let watch_version = match watch_options.version {
                    Some(value) => value.to_string(),
                    _ => "7.2.1".to_string(), // current version
                };
                task_config.install_crate_args = Some(vec!["--version".to_string(), watch_version]);

                match watch_options.postpone {
                    Some(value) => {
                        if value {
                            watch_args.push("--postpone".to_string());
                        }
                    }
                    _ => (),
                };

                match watch_options.ignore_pattern {
                    Some(value) => watch_args.extend_from_slice(&["-i".to_string(), value]),
                    _ => (),
                };

                match watch_options.no_git_ignore {
                    Some(value) => {
                        if value {
                            watch_args.push("--no-gitignore".to_string());
                        }
                    }
                    _ => (),
                };
            }
            _ => (),
        },
        _ => (),
    }

    watch_args.extend_from_slice(&["-x".to_string(), make_command.to_string()]);

    task_config.args = Some(watch_args);

    task_config
}

fn create_proxy_task(task: &str, allow_private: bool, skip_init_end_tasks: bool) -> Task {
    //get log level name
    let log_level = logger::get_log_level();

    let mut log_level_arg = "--loglevel=".to_string();
    log_level_arg.push_str(&log_level);

    //get profile
    let profile_name = profile::get();

    let mut profile_arg = "--profile=".to_string();
    profile_arg.push_str(&profile_name);

    //setup common args
    let mut args = vec![
        "make".to_string(),
        "--disable-check-for-updates".to_string(),
        "--no-on-error".to_string(),
        log_level_arg.to_string(),
        profile_arg.to_string(),
    ];

    if allow_private {
        args.push("--allow-private".to_string());
    }

    if skip_init_end_tasks {
        args.push("--skip-init-end-tasks".to_string());
    }

    //get makefile location
    match env::var("CARGO_MAKE_MAKEFILE_PATH") {
        Ok(makefile_path) => {
            if makefile_path.len() > 0 {
                let mut makefile_arg = "--makefile=".to_string();
                makefile_arg.push_str(&makefile_path);

                args.push(makefile_arg.to_string());
            }
        }
        _ => {}
    };

    args.push(task.to_string());

    let mut proxy_task = Task::new();
    proxy_task.command = Some("cargo".to_string());
    proxy_task.args = Some(args);

    proxy_task.get_normalized_task()
}

fn run_flow(flow_info: &FlowInfo, sub_flow: bool) {
    let allow_private = sub_flow || flow_info.allow_private;

    let execution_plan = create_execution_plan(
        &flow_info.config,
        &flow_info.task,
        flow_info.disable_workspace,
        allow_private,
        sub_flow,
    );
    debug!("Created execution plan: {:#?}", &execution_plan);

    run_task_flow(&flow_info, &execution_plan);
}

fn run_protected_flow(flow_info: &FlowInfo) {
    let proxy_task = create_proxy_task(
        &flow_info.task,
        flow_info.allow_private,
        flow_info.skip_init_end_tasks,
    );

    let exit_code = command::run_command(&proxy_task.command.unwrap(), &proxy_task.args, false);

    if exit_code != 0 {
        match flow_info.config.config.on_error_task {
            Some(ref on_error_task) => {
                let mut error_flow_info = flow_info.clone();
                error_flow_info.disable_on_error = true;
                error_flow_info.task = on_error_task.clone();

                run_flow(&error_flow_info, false);
            }
            _ => (),
        };

        error!("Task error detected, exit code: {}", &exit_code);
    }
}

/// Runs the requested tasks.

/// The flow is as follows:
///
/// * Create an execution plan based on the requested task and its dependencies
/// * Run all tasks defined in the execution plan
pub(crate) fn run(config: Config, task: &str, env_info: EnvInfo, cli_args: &CliArgs) {
    let start_time = SystemTime::now();

    let flow_info = FlowInfo {
        config,
        task: task.to_string(),
        env_info,
        disable_workspace: cli_args.disable_workspace,
        disable_on_error: cli_args.disable_on_error,
        allow_private: cli_args.allow_private,
        skip_init_end_tasks: cli_args.skip_init_end_tasks,
        cli_arguments: cli_args.arguments.clone(),
    };

    if flow_info.disable_on_error || flow_info.config.config.on_error_task.is_none() {
        run_flow(&flow_info, false);
    } else {
        run_protected_flow(&flow_info);
    }

    let time_string = match start_time.elapsed() {
        Ok(elapsed) => {
            let mut string = "in ".to_string();
            string.push_str(&elapsed.as_secs().to_string());
            string.push_str(" seconds");

            string
        }
        _ => "".to_string(),
    };

    info!("Build Done {}.", &time_string);
}
<commit_msg>upgrade cargo watch to 7.3.0<commit_after>//! # runner
//!
//! Runs the requested tasks.

//! The flow is as follows:
//!
//! * Load env variables
//! * Create an execution plan based on the requested task and its dependencies
//! * Run all tasks defined in the execution plan
//!

#[cfg(test)]
#[path = "./runner_test.rs"]
mod runner_test;

use crate::command;
use crate::condition;
use crate::environment;
use crate::execution_plan::create as create_execution_plan;
use crate::functions;
use crate::installer;
use crate::logger;
use crate::profile;
use crate::scriptengine;
use crate::types::{
    CliArgs, Config, DeprecationInfo, EnvInfo, EnvValue, ExecutionPlan, FlowInfo, RunTaskInfo,
    RunTaskName, RunTaskRoutingInfo, Step, Task, TaskWatchOptions,
};
use indexmap::IndexMap;
use std::env;
use std::thread;
use std::time::SystemTime;

fn do_in_task_working_directory<F>(step: &Step, mut action: F)
where
    F: FnMut(),
{
    let revert_directory = match step.config.cwd {
        Some(ref cwd) => {
            let expanded_cwd = environment::expand_value(cwd);

            if expanded_cwd.len() > 0 {
                let directory = envmnt::get_or("CARGO_MAKE_WORKING_DIRECTORY", "");

                environment::setup_cwd(Some(&expanded_cwd));

                directory
            } else {
                "".to_string()
            }
        }
        None => "".to_string(),
    };

    action();

    // revert to original cwd
    match step.config.cwd {
        Some(_) => environment::setup_cwd(Some(&revert_directory)),
        _ => (),
    };
}

fn validate_condition(flow_info: &FlowInfo, step: &Step) -> bool {
    let mut valid = true;

    let do_validate = || {
        valid = condition::validate_condition_for_step(&flow_info, &step);
    };

    do_in_task_working_directory(&step, do_validate);

    valid
}

pub(crate) fn get_sub_task_info_for_routing_info(
    flow_info: &FlowInfo,
    routing_info: &Vec<RunTaskRoutingInfo>,
) -> (Option<Vec<String>>, bool, bool) {
    let mut task_name = None;

    let mut fork = false;
    let mut parallel = false;
    for routing_step in routing_info {
        let invoke = condition::validate_conditions(
            &flow_info,
            &routing_step.condition,
            &routing_step.condition_script,
            None,
        );

        if invoke {
            let task_name_values = match routing_step.name.clone() {
                RunTaskName::Single(name) => vec![name],
                RunTaskName::Multiple(names) => names,
            };
            task_name = Some(task_name_values);
            fork = routing_step.fork.unwrap_or(false);
            parallel = routing_step.parallel.unwrap_or(false);
            break;
        }
    }

    (task_name, fork, parallel)
}

fn create_fork_step(flow_info: &FlowInfo) -> Step {
    let fork_task = create_proxy_task(&flow_info.task, true, true);

    Step {
        name: "cargo_make_run_fork".to_string(),
        config: fork_task,
    }
}

fn run_forked_task(flow_info: &FlowInfo) {
    // run task as a sub process
    let step = create_fork_step(&flow_info);
    run_task(&flow_info, &step);
}

/// runs a sub task and returns true/false based if a sub task was actually invoked
fn run_sub_task_and_report(flow_info: &FlowInfo, sub_task: &RunTaskInfo) -> bool {
    let (task_names, fork, parallel) = match sub_task {
        RunTaskInfo::Name(ref name) => (Some(vec![name.to_string()]), false, false),
        RunTaskInfo::Details(ref details) => {
            let task_name_values = match details.name.clone() {
                RunTaskName::Single(name) => vec![name],
                RunTaskName::Multiple(names) => names,
            };
            (
                Some(task_name_values),
                details.fork.unwrap_or(false),
                details.parallel.unwrap_or(false),
            )
        }
        RunTaskInfo::Routing(ref routing_info) => {
            get_sub_task_info_for_routing_info(&flow_info, routing_info)
        }
    };

    if task_names.is_some() {
        let names = task_names.unwrap();
        let mut threads = vec![];

        for name in names {
            let task_run_fn = |flow_info: &FlowInfo, fork: bool| {
                let mut sub_flow_info = flow_info.clone();
                sub_flow_info.task = name;

                if fork {
                    run_forked_task(&sub_flow_info);
                } else {
                    run_flow(&sub_flow_info, true);
                }
            };

            if parallel {
                let run_flow_info = flow_info.clone();
                threads.push(thread::spawn(move || {
                    task_run_fn(&run_flow_info, fork);
                }));
            } else {
                task_run_fn(&flow_info, fork);
            }
        }

        if threads.len() > 0 {
            for task_thread in threads {
                task_thread.join().unwrap();
            }
        }

        true
    } else {
        false
    }
}

fn run_sub_task(flow_info: &FlowInfo, sub_task: &RunTaskInfo) {
    run_sub_task_and_report(&flow_info, &sub_task);
}

fn create_watch_task_name(task: &str) -> String {
    let mut watch_task_name = "".to_string();
    watch_task_name.push_str(&task);
    watch_task_name.push_str("-watch");

    watch_task_name
}

fn create_watch_step(task: &str, options: Option<TaskWatchOptions>) -> Step {
    let watch_task = create_watch_task(&task, options);

    let watch_task_name = create_watch_task_name(&task);

    Step {
        name: watch_task_name,
        config: watch_task,
    }
}

fn watch_task(flow_info: &FlowInfo, task: &str, options: Option<TaskWatchOptions>) {
    let step = create_watch_step(&task, options);

    run_task(&flow_info, &step);
}

fn is_watch_enabled() -> bool {
    !envmnt::is_or("CARGO_MAKE_DISABLE_WATCH", false)
}

fn should_watch(task: &Task) -> bool {
    match task.watch {
        Some(ref watch_value) => match watch_value {
            TaskWatchOptions::Boolean(watch_bool) => {
                if *watch_bool {
                    is_watch_enabled()
                } else {
                    false
                }
            }
            TaskWatchOptions::Options(_) => is_watch_enabled(),
        },
        None => false,
    }
}

fn run_task(flow_info: &FlowInfo, step: &Step) {
    match step.config.env {
        Some(ref env) => environment::set_current_task_meta_info_env(env.clone()),
        None => (),
    };

    if validate_condition(&flow_info, &step) {
        info!("Running Task: {}", &step.name);

        if !step.config.is_valid() {
            error!(
                "Invalid task, contains multiple actions.\n{:#?}",
                &step.config
            );
        }

        let deprecated_info = step.config.deprecated.clone();
        match deprecated_info {
            Some(deprecated) => match deprecated {
                DeprecationInfo::Boolean(value) => {
                    if value {
                        warn!("Task: {} is deprecated.", &step.name);
                    }

                    ()
                }
                DeprecationInfo::Message(ref message) => {
                    warn!("Task: {} is deprecated - {}", &step.name, message);

                    ()
                }
            },
            None => (),
        };

        //get profile
        let profile_name = profile::get();

        match step.config.env_files {
            Some(ref env_files) => environment::set_env_files(env_files.clone()),
            None => (),
        };
        match step.config.env {
            Some(ref env) => environment::set_env(env.clone()),
            None => (),
        };

        envmnt::set("CARGO_MAKE_CURRENT_TASK_NAME", &step.name);

        //make sure profile env is not overwritten
        profile::set(&profile_name);

        // modify step using env and functions
        let mut updated_step = functions::run(&step);
        updated_step = environment::expand_env(&updated_step);

        let watch = should_watch(&step.config);

        if watch {
            watch_task(&flow_info, &step.name, step.config.watch.clone());
        } else {
            do_in_task_working_directory(&step, || {
                installer::install(&updated_step.config);
            });

            match step.config.run_task {
                Some(ref sub_task) => run_sub_task(&flow_info, sub_task),
                None => {
                    do_in_task_working_directory(&step, || {
                        // get cli arguments
                        let cli_arguments = match flow_info.cli_arguments {
                            Some(ref args) => args.clone(),
                            None => vec![],
                        };

                        // run script
                        let script_runner_done =
                            scriptengine::invoke(&updated_step.config, &cli_arguments);

                        // run command
                        if !script_runner_done {
                            command::run(&updated_step);
                        };
                    });
                }
            };
        }
    } else {
        let fail_message = match step.config.condition {
            Some(ref condition) => match condition.fail_message {
                Some(ref value) => value.to_string(),
                None => "".to_string(),
            },
            None => "".to_string(),
        };
        info!("Skipping Task: {} {}", &step.name, &fail_message);
    }
}

fn run_task_flow(flow_info: &FlowInfo, execution_plan: &ExecutionPlan) {
    for step in &execution_plan.steps {
        run_task(&flow_info, &step);
    }
}

fn create_watch_task(task: &str, options: Option<TaskWatchOptions>) -> Task {
    let mut task_config = create_proxy_task(&task, true, true);

    let mut env_map = task_config.env.unwrap_or(IndexMap::new());
    env_map.insert(
        "CARGO_MAKE_DISABLE_WATCH".to_string(),
        EnvValue::Value("true".to_string()),
    );
    task_config.env = Some(env_map);

    let make_args = task_config.args.unwrap();
    let make_command = &make_args.join(" ");

    let mut watch_args = vec!["watch".to_string(), "-q".to_string()];

    match options {
        Some(task_watch_options) => match task_watch_options {
            TaskWatchOptions::Options(watch_options) => {
                let watch_version = match watch_options.version {
                    Some(value) => value.to_string(),
                    _ => "7.3.0".to_string(), // current version
                };
                task_config.install_crate_args = Some(vec!["--version".to_string(), watch_version]);

                match watch_options.postpone {
                    Some(value) => {
                        if value {
                            watch_args.push("--postpone".to_string());
                        }
                    }
                    _ => (),
                };

                match watch_options.ignore_pattern {
                    Some(value) => watch_args.extend_from_slice(&["-i".to_string(), value]),
                    _ => (),
                };

                match watch_options.no_git_ignore {
                    Some(value) => {
                        if value {
                            watch_args.push("--no-gitignore".to_string());
                        }
                    }
                    _ => (),
                };
            }
            _ => (),
        },
        _ => (),
    }

    watch_args.extend_from_slice(&["-x".to_string(), make_command.to_string()]);

    task_config.args = Some(watch_args);

    task_config
}

fn create_proxy_task(task: &str, allow_private: bool, skip_init_end_tasks: bool) -> Task {
    //get log level name
    let log_level = logger::get_log_level();

    let mut log_level_arg = "--loglevel=".to_string();
    log_level_arg.push_str(&log_level);

    //get profile
    let profile_name = profile::get();

    let mut profile_arg = "--profile=".to_string();
    profile_arg.push_str(&profile_name);

    //setup common args
    let mut args = vec![
        "make".to_string(),
        "--disable-check-for-updates".to_string(),
        "--no-on-error".to_string(),
        log_level_arg.to_string(),
        profile_arg.to_string(),
    ];

    if allow_private {
        args.push("--allow-private".to_string());
    }

    if skip_init_end_tasks {
        args.push("--skip-init-end-tasks".to_string());
    }

    //get makefile location
    match env::var("CARGO_MAKE_MAKEFILE_PATH") {
        Ok(makefile_path) => {
            if makefile_path.len() > 0 {
                let mut makefile_arg = "--makefile=".to_string();
                makefile_arg.push_str(&makefile_path);

                args.push(makefile_arg.to_string());
            }
        }
        _ => {}
    };

    args.push(task.to_string());

    let mut proxy_task = Task::new();
    proxy_task.command = Some("cargo".to_string());
    proxy_task.args = Some(args);

    proxy_task.get_normalized_task()
}

fn run_flow(flow_info: &FlowInfo, sub_flow: bool) {
    let allow_private = sub_flow || flow_info.allow_private;

    let execution_plan = create_execution_plan(
        &flow_info.config,
        &flow_info.task,
        flow_info.disable_workspace,
        allow_private,
        sub_flow,
    );
    debug!("Created execution plan: {:#?}", &execution_plan);

    run_task_flow(&flow_info, &execution_plan);
}

fn run_protected_flow(flow_info: &FlowInfo) {
    let proxy_task = create_proxy_task(
        &flow_info.task,
        flow_info.allow_private,
        flow_info.skip_init_end_tasks,
    );

    let exit_code = command::run_command(&proxy_task.command.unwrap(), &proxy_task.args, false);

    if exit_code != 0 {
        match flow_info.config.config.on_error_task {
            Some(ref on_error_task) => {
                let mut error_flow_info = flow_info.clone();
                error_flow_info.disable_on_error = true;
                error_flow_info.task = on_error_task.clone();

                run_flow(&error_flow_info, false);
            }
            _ => (),
        };

        error!("Task error detected, exit code: {}", &exit_code);
    }
}

/// Runs the requested tasks.

/// The flow is as follows:
///
/// * Create an execution plan based on the requested task and its dependencies
/// * Run all tasks defined in the execution plan
pub(crate) fn run(config: Config, task: &str, env_info: EnvInfo, cli_args: &CliArgs) {
    let start_time = SystemTime::now();

    let flow_info = FlowInfo {
        config,
        task: task.to_string(),
        env_info,
        disable_workspace: cli_args.disable_workspace,
        disable_on_error: cli_args.disable_on_error,
        allow_private: cli_args.allow_private,
        skip_init_end_tasks: cli_args.skip_init_end_tasks,
        cli_arguments: cli_args.arguments.clone(),
    };

    if flow_info.disable_on_error || flow_info.config.config.on_error_task.is_none() {
        run_flow(&flow_info, false);
    } else {
        run_protected_flow(&flow_info);
    }

    let time_string = match start_time.elapsed() {
        Ok(elapsed) => {
            let mut string = "in ".to_string();
            string.push_str(&elapsed.as_secs().to_string());
            string.push_str(" seconds");

            string
        }
        _ => "".to_string(),
    };

    info!("Build Done {}.", &time_string);
}
<|endoftext|>