ckb_capsule/
project_context.rs

1/// Project Context
2use crate::config::{Config, Deployment};
3use crate::version::Version;
4use anyhow::{anyhow, Result};
5use log::error;
6use std::env;
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10
11pub const CONTRACTS_DIR: &str = "contracts";
12const CONTRACTS_BUILD_DIR: &str = "build";
13const MIGRATIONS_DIR: &str = "migrations";
14pub const CONFIG_FILE: &str = "capsule.toml";
15pub const CARGO_CONFIG_FILE: &str = "Cargo.toml";
16
17#[derive(Debug, Copy, Clone, PartialEq, Eq)]
18pub enum BuildEnv {
19    Debug,
20    Release,
21}
22
23impl FromStr for BuildEnv {
24    type Err = &'static str;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        match s.to_lowercase().as_str() {
28            "debug" => Ok(BuildEnv::Debug),
29            "release" => Ok(BuildEnv::Release),
30            _ => Err("no match"),
31        }
32    }
33}
34
35#[derive(Debug, Copy, Clone)]
36pub struct BuildConfig {
37    pub build_env: BuildEnv,
38    pub always_debug: bool,
39    // Remap prefix paths on release build.
40    pub remap: bool,
41}
42
43#[derive(Debug, Copy, Clone)]
44pub enum DeployEnv {
45    Dev,
46    Production,
47}
48
49impl FromStr for DeployEnv {
50    type Err = &'static str;
51
52    fn from_str(s: &str) -> Result<Self, Self::Err> {
53        match s.to_lowercase().as_str() {
54            "dev" => Ok(DeployEnv::Dev),
55            "production" => Ok(DeployEnv::Production),
56            _ => Err("no match"),
57        }
58    }
59}
60
61#[derive(Clone)]
62pub struct Context {
63    pub project_path: PathBuf,
64    pub config: Config,
65    pub use_docker_host: bool,
66    pub docker_env_file: String,
67    pub rustup_dir: Option<String>,
68}
69
70impl Context {
71    pub fn load() -> Result<Context> {
72        Self::load_from_path(env::current_dir()?)
73    }
74
75    pub fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Context> {
76        let mut project_path = PathBuf::new();
77        project_path.push(&path);
78        let content = {
79            let mut config_path = project_path.clone();
80            config_path.push(CONFIG_FILE);
81            read_config_file(config_path)?
82        };
83        let config: Config = toml_edit::de::from_str(&content).expect("parse config");
84        let capsule_version = Version::current();
85        let project_version: Version = config.version.parse()?;
86        if !capsule_version.is_compatible(&project_version) {
87            return Err(anyhow!(
88                "Incompatible capsule version {}, this project requires a version that's compatible with {}",
89                capsule_version.to_string(),
90                project_version.to_string()
91            ));
92        }
93        Ok(Context {
94            config,
95            project_path,
96            use_docker_host: false,
97            docker_env_file: String::new(),
98            rustup_dir: None,
99        })
100    }
101
102    pub fn workspace_dir(&self) -> Result<PathBuf> {
103        let mut path = self.project_path.clone();
104        if let Some(workspace_dir) = self.config.rust.workspace_dir.as_ref() {
105            match workspace_dir.to_str() {
106                Some(".") => {}
107                Some(CONTRACTS_DIR) => {
108                    path.push(workspace_dir);
109                }
110                dir => {
111                    return Err(anyhow!("Invalid `workspace_dir` config: {:?}, only allowed \".\" or \"contracts\".", dir));
112                }
113            }
114        }
115        Ok(path)
116    }
117
118    pub fn contracts_path(&self) -> PathBuf {
119        let mut path = self.project_path.clone();
120        path.push(CONTRACTS_DIR);
121        path
122    }
123
124    pub fn contracts_build_dir(&self) -> PathBuf {
125        let mut path = self.project_path.clone();
126        path.push(CONTRACTS_BUILD_DIR);
127        path
128    }
129
130    pub fn contracts_build_path(&self, env: BuildEnv) -> PathBuf {
131        let mut path = self.contracts_build_dir();
132        let prefix = match env {
133            BuildEnv::Debug => "debug",
134            BuildEnv::Release => "release",
135        };
136        path.push(prefix);
137        path
138    }
139
140    pub fn migrations_path(&self, env: DeployEnv) -> PathBuf {
141        let mut path = self.project_path.clone();
142        path.push(MIGRATIONS_DIR);
143        let prefix = match env {
144            DeployEnv::Production => "production",
145            DeployEnv::Dev => "dev",
146        };
147        path.push(prefix);
148        path
149    }
150
151    pub fn load_deployment(&self) -> Result<Deployment> {
152        let mut path = self.project_path.clone();
153        path.push(
154            self.config
155                .deployment
156                .as_deref()
157                .unwrap_or_else(|| Path::new("deployment.toml")),
158        );
159        match toml_edit::de::from_str(&fs::read_to_string(&path)?) {
160            Ok(deployment) => Ok(deployment),
161            Err(err) => {
162                error!("failed to parse {:?}", path);
163                Err(err.into())
164            }
165        }
166    }
167}
168
169pub fn read_config_file<P: AsRef<Path> + std::fmt::Debug>(path: P) -> Result<String> {
170    match fs::read_to_string(&path) {
171        Ok(content) => Ok(content),
172        Err(err) => Err(anyhow!(
173            "Can't found {:?}, current directory is not a project. error: {:?}",
174            path,
175            err
176        )),
177    }
178}
179
180pub fn write_config_file<P: AsRef<Path>>(path: P, content: String) -> Result<()> {
181    fs::write(path, content)?;
182    Ok(())
183}