Skip to main content

ab_contracts_tooling/
target_specification.rs

1//! Abundance target specification for contracts
2
3use anyhow::Context;
4use dirs::cache_dir;
5use std::fs::{File, create_dir_all};
6use std::io::{Read, Seek, Write};
7use std::path::{Path, PathBuf};
8
9pub(crate) const TARGET_SPECIFICATION_NAME: &str = "riscv64-unknown-none-abundance";
10const TARGET_SPECIFICATION_FILE_NAME: &str = "riscv64-unknown-none-abundance.json";
11const TARGET_SPECIFICATION: &str = include_str!("riscv64-unknown-none-abundance.json");
12
13/// Target specification for contracts
14#[derive(Debug)]
15pub struct TargetSpecification {
16    path: PathBuf,
17    _file: File,
18}
19
20impl TargetSpecification {
21    /// Create a target specification instance.
22    ///
23    /// `base_directory` is used to store the target specification JSON file.
24    pub fn create(base_directory: &Path) -> anyhow::Result<Self> {
25        let path = base_directory.join(TARGET_SPECIFICATION_FILE_NAME);
26        let mut file = File::options()
27            .read(true)
28            .write(true)
29            .create(true)
30            .truncate(false)
31            .open(&path)
32            .context("Failed to open target specification file")?;
33
34        // Ensure the target specification file has expected content
35        loop {
36            file.lock_shared()
37                .context("Failed to lock target specification file")?;
38
39            let mut actual_target_specification = String::with_capacity(TARGET_SPECIFICATION.len());
40            file.seek(std::io::SeekFrom::Start(0))
41                .context("Failed to seek to start of target specification file")?;
42            file.read_to_string(&mut actual_target_specification)
43                .context("Failed to read target specification file")?;
44
45            if actual_target_specification == TARGET_SPECIFICATION {
46                break;
47            }
48
49            file.unlock()
50                .context("Failed to unlock target specification file")?;
51            file.lock()
52                .context("Failed to lock target specification file")?;
53            file.set_len(0)
54                .context("Failed to truncate target specification file")?;
55            file.seek(std::io::SeekFrom::Start(0))
56                .context("Failed to seek to start of target specification file")?;
57            file.write_all(TARGET_SPECIFICATION.as_bytes())
58                .context("Failed to write target specification file")?;
59            file.sync_all()
60                .context("Failed to sync target specification file")?;
61            file.unlock()
62                .context("Failed to unlock target specification file")?;
63        }
64
65        Ok(Self { path, _file: file })
66    }
67
68    /// Create (if not exists) and return the default base directory used for storing the target
69    /// specifications JSON file
70    pub fn default_base_dir() -> anyhow::Result<PathBuf> {
71        let app_dir = cache_dir()
72            .context("Failed to get cache directory")?
73            .join("ab-contracts");
74        create_dir_all(&app_dir)
75            .with_context(|| format!("Failed to create cache directory {}", app_dir.display()))?;
76
77        Ok(app_dir)
78    }
79
80    /// Get the path to the target specification JSON file
81    pub fn path(&self) -> &Path {
82        &self.path
83    }
84}