cargo_openvm/
util.rs

1use std::{
2    fs::{read_dir, read_to_string},
3    path::{Path, PathBuf},
4};
5
6use eyre::{Context, Result};
7use openvm_build::{get_in_scope_packages, get_workspace_packages};
8use openvm_sdk::config::AppConfig;
9use openvm_sdk_config::SdkVmConfig;
10use serde::de::DeserializeOwned;
11
12use crate::{
13    commands::RunCargoArgs,
14    default::{
15        default_app_config, BASELINE_JSON_EXT, COMMIT_JSON_EXT, DEFAULT_AGG_PREFIX_PK_NAME,
16        DEFAULT_APP_PK_NAME, DEFAULT_APP_VK_NAME,
17    },
18};
19
20/// Versioned directory name for the EVM halo2 verifier artifacts stored under
21/// `~/.openvm/halo2/src/` (e.g. `v2.0-base`). The `-base` suffix matches the
22/// openvm-solidity-sdk layout for verifiers with deferrals disabled, which is the only
23/// config `cargo openvm setup --evm` generates.
24pub fn evm_verifier_version_dir() -> String {
25    format!("v{}-base", openvm_sdk::OPENVM_VERSION)
26}
27
28pub(crate) fn read_to_struct_toml<T: DeserializeOwned>(path: impl AsRef<Path>) -> Result<T> {
29    let path = path.as_ref();
30    let toml = read_to_string(path)
31        .with_context(|| format!("failed to read config file {}", path.display()))?;
32    let ret = toml::from_str(&toml)
33        .with_context(|| format!("failed to parse TOML from {}", path.display()))?;
34    Ok(ret)
35}
36
37pub fn read_config_toml_or_default(config: impl AsRef<Path>) -> Result<AppConfig<SdkVmConfig>> {
38    if config.as_ref().exists() {
39        read_to_struct_toml(config)
40    } else {
41        println!(
42            "{:?} not found, using default application configuration",
43            config.as_ref()
44        );
45        Ok(default_app_config())
46    }
47}
48
49pub fn find_manifest_dir(mut current_dir: PathBuf) -> Result<PathBuf> {
50    current_dir = current_dir.canonicalize()?;
51    while !current_dir.join("Cargo.toml").exists() {
52        current_dir = current_dir
53            .parent()
54            .ok_or_else(|| {
55                eyre::eyre!(
56                    "could not find Cargo.toml in current directory or any parent directory"
57                )
58            })?
59            .to_path_buf();
60    }
61    Ok(current_dir)
62}
63
64pub fn get_manifest_path_and_dir(manifest_path: &Option<PathBuf>) -> Result<(PathBuf, PathBuf)> {
65    let manifest_dir = if let Some(manifest_path) = &manifest_path {
66        if !manifest_path.ends_with("Cargo.toml") {
67            return Err(eyre::eyre!(
68                "manifest_path must be a path to a Cargo.toml file"
69            ));
70        }
71        manifest_path.parent().unwrap().canonicalize()?
72    } else {
73        find_manifest_dir(PathBuf::from("."))?
74    };
75    let manifest_path = manifest_dir.join("Cargo.toml");
76    Ok((manifest_path.clone(), manifest_dir))
77}
78
79pub fn get_target_dir(target_dir: &Option<PathBuf>, manifest_path: &PathBuf) -> PathBuf {
80    target_dir
81        .clone()
82        .unwrap_or_else(|| openvm_build::get_target_dir(manifest_path))
83}
84
85pub fn get_target_output_dir(target_dir: &Path, profile: &str) -> PathBuf {
86    get_openvm_dir(target_dir).join(profile)
87}
88
89pub fn get_openvm_dir(target_dir: &Path) -> PathBuf {
90    target_dir.parent().unwrap_or(target_dir).join("openvm")
91}
92
93pub fn get_app_pk_path(target_dir: &Path) -> PathBuf {
94    get_openvm_dir(target_dir).join(DEFAULT_APP_PK_NAME)
95}
96
97pub fn get_app_vk_path(target_dir: &Path) -> PathBuf {
98    get_openvm_dir(target_dir).join(DEFAULT_APP_VK_NAME)
99}
100
101pub fn get_agg_prefix_pk_path(target_dir: &Path) -> PathBuf {
102    get_openvm_dir(target_dir).join(DEFAULT_AGG_PREFIX_PK_NAME)
103}
104
105pub fn get_app_commit_path(target_output_dir: &Path, target_name: PathBuf) -> PathBuf {
106    let commit_name = target_name.with_extension(COMMIT_JSON_EXT);
107    target_output_dir.join(commit_name)
108}
109
110pub fn get_app_baseline_path(target_output_dir: &Path, target_name: PathBuf) -> PathBuf {
111    let baseline_name = target_name.with_extension(BASELINE_JSON_EXT);
112    target_output_dir.join(baseline_name)
113}
114
115// Given the arguments to a run command, this function isolates the executable to
116// run. If a specific binary or example is specified it will return that, else it
117// will search the workspace/package for binary targets. If there is a single
118// binary that will be returned, else an error will be raised.
119pub fn get_single_target_name(cargo_args: &RunCargoArgs) -> Result<PathBuf> {
120    get_single_target_name_raw(
121        &cargo_args.bin,
122        &cargo_args.example,
123        &cargo_args.manifest.manifest_path,
124        &cargo_args.package,
125    )
126}
127
128pub fn get_single_target_name_raw(
129    bin: &[String],
130    example: &[String],
131    manifest_path: &Option<PathBuf>,
132    package: &Option<String>,
133) -> Result<PathBuf> {
134    let num_targets = bin.len() + example.len();
135    let single_target_name = if num_targets > 1 {
136        return Err(eyre::eyre!(
137            "`cargo openvm run` can run at most one executable, but multiple were specified"
138        ));
139    } else if num_targets == 0 {
140        let (_, manifest_dir) = get_manifest_path_and_dir(manifest_path)?;
141
142        let packages = if package.is_some() {
143            get_workspace_packages(&manifest_dir)
144        } else {
145            get_in_scope_packages(&manifest_dir)
146        }
147        .into_iter()
148        .filter(|pkg| {
149            if let Some(package) = package {
150                pkg.name == *package
151            } else {
152                true
153            }
154        })
155        .collect::<Vec<_>>();
156
157        let binaries = packages
158            .iter()
159            .flat_map(|pkg| pkg.targets.iter())
160            .filter(|t| t.is_bin())
161            .collect::<Vec<_>>();
162
163        if binaries.len() > 1 {
164            return Err(eyre::eyre!(
165                "Could not determine which binary to run. Use the --bin flag to specify.\n\
166                    Available targets: {:?}",
167                binaries.iter().map(|t| t.name.clone()).collect::<Vec<_>>()
168            ));
169        } else if binaries.is_empty() {
170            return Err(eyre::eyre!(
171                "No binaries found. If you would like to run an example, use the --example flag.",
172            ));
173        } else {
174            PathBuf::from(binaries[0].name.clone())
175        }
176    } else if bin.is_empty() {
177        PathBuf::from("examples").join(&example[0])
178    } else {
179        PathBuf::from(bin[0].clone())
180    };
181    Ok(single_target_name)
182}
183
184pub fn resolve_proof_path(proof: &Option<PathBuf>, extension: &str) -> Result<PathBuf> {
185    if let Some(proof) = proof {
186        return Ok(proof.clone());
187    }
188    let files = get_files_with_ext(Path::new("."), extension)?;
189    if files.len() > 1 {
190        return Err(eyre::eyre!(
191            "multiple .{extension} files found, please specify the path using option --proof"
192        ));
193    } else if files.is_empty() {
194        return Err(eyre::eyre!(
195            "no .{extension} file found, please specify the path using option --proof"
196        ));
197    }
198    Ok(files[0].clone())
199}
200
201pub fn get_files_with_ext(dir: &Path, extension: &str) -> Result<Vec<PathBuf>> {
202    let dir = dir.canonicalize()?;
203    let mut files = Vec::new();
204    for entry in read_dir(dir)? {
205        let path = entry?.path();
206        if path.is_file()
207            && path
208                .to_str()
209                .is_some_and(|path_str| path_str.ends_with(extension))
210        {
211            files.push(path);
212        }
213    }
214    Ok(files)
215}