From 939c217dc25cfd38824733e4359ca9a3adeb5276 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:18:00 -0500 Subject: [PATCH 1/8] Fetch possible example contracts at compile time --- cmd/soroban-cli/Cargo.toml | 3 ++ cmd/soroban-cli/build.rs | 41 +++++++++++++++++++ cmd/soroban-cli/src/commands/contract/init.rs | 31 +------------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index f5ef2b1b0..afd0103fa 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -115,6 +115,9 @@ openssl = { version = "=0.10.55", features = ["vendored"] } [build-dependencies] crate-git-revision = "0.0.4" +serde.workspace = true +ureq = { version = "2.9.1", features = ["json"] } + [dev-dependencies] assert_cmd = "2.0.4" diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index b6e6dd92a..8fe6bf4b8 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -1,3 +1,44 @@ fn main() { crate_git_revision::init(); + let w = &mut std::io::stdout(); + __set_example_contracts(w).unwrap(); +} + +fn __set_example_contracts(w: &mut impl std::io::Write) -> std::io::Result<()> { + let example_contracts = get_example_contracts().unwrap().join(","); + writeln!(w, "cargo:rustc-env=EXAMPLE_CONTRACTS={example_contracts}")?; + Ok(()) +} + +const GITHUB_API_URL: &str = + "https://api.github.com/repos/stellar/soroban-examples/git/trees/main?recursive=1"; + +#[derive(serde::Deserialize, Debug)] +struct RepoPath { + path: String, + #[serde(rename = "type")] + type_field: String, +} + +#[derive(serde::Deserialize, Debug)] +struct ReqBody { + tree: Vec, +} + +fn get_example_contracts() -> Result, ureq::Error> { + let body: ReqBody = ureq::get(GITHUB_API_URL).call()?.into_json()?; + let mut valid_examples = Vec::new(); + for item in body.tree { + if item.type_field == "blob" + || item.path.starts_with('.') + || item.path.contains('/') + || item.path == "hello_world" + { + continue; + } + + valid_examples.push(item.path); + } + + Ok(valid_examples) } diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 6dfac7fac..731af94a3 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -45,35 +45,8 @@ pub struct Cmd { } fn possible_example_values() -> ValueParser { - let parser = PossibleValuesParser::new( - [ - "account", - "alloc", - "atomic_multiswap", - "atomic_swap", - "auth", - "cross_contract", - "custom_types", - "deep_contract_auth", - "deployer", - "errors", - "eth_abi", - "events", - "fuzzing", - "increment", - "liquidity_pool", - "logging", - "mint-lock", - "simple_account", - "single_offer", - "timelock", - "token", - "upgradeable_contract", - "workspace", - ] - .iter() - .map(PossibleValue::new), - ); + let example_contracts = env!("EXAMPLE_CONTRACTS").split(',').collect::>(); + let parser = PossibleValuesParser::new(example_contracts.iter().map(PossibleValue::new)); parser.into() } From 46cbe05702d46b971b86201156419e137da55bca Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:51:04 -0500 Subject: [PATCH 2/8] Removed unused/unnecessary functionality in init.rs --- cmd/soroban-cli/Cargo.toml | 1 + cmd/soroban-cli/build.rs | 16 ++++++++++++++-- cmd/soroban-cli/src/commands/contract/init.rs | 17 ++++------------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index afd0103fa..666b8c456 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -116,6 +116,7 @@ openssl = { version = "=0.10.55", features = ["vendored"] } [build-dependencies] crate-git-revision = "0.0.4" serde.workspace = true +thiserror.workspace = true ureq = { version = "2.9.1", features = ["json"] } diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index 8fe6bf4b8..69efe8f2e 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -25,8 +25,20 @@ struct ReqBody { tree: Vec, } -fn get_example_contracts() -> Result, ureq::Error> { - let body: ReqBody = ureq::get(GITHUB_API_URL).call()?.into_json()?; +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Failed to complete get request")] + UreqError(#[from] Box), + + #[error("Io error: {0}")] + IoError(#[from] std::io::Error), +} + +fn get_example_contracts() -> Result, Error> { + let body: ReqBody = ureq::get(GITHUB_API_URL) + .call() + .map_err(Box::new)? + .into_json()?; let mut valid_examples = Vec::new(); for item in body.tree { if item.type_field == "blob" diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 731af94a3..49cf9b857 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -16,10 +16,12 @@ use gix::{clone, create, open, progress, remote}; use rust_embed::RustEmbed; use serde_json::{from_str, json, Error as JsonError, Value as JsonValue}; use toml_edit::{Document, Formatted, InlineTable, Item, TomlError, Value as TomlValue}; -use ureq::{get, Error as UreqError}; +use ureq::get; const SOROBAN_EXAMPLES_URL: &str = "https://github.com/stellar/soroban-examples.git"; const GITHUB_URL: &str = "https://github.com"; +const WITH_EXAMPLE_LONG_HELP_TEXT: &str = + "An optional flag to specify Soroban example contracts to include. A hello-world contract will be included by default."; #[derive(Clone, Debug, ValueEnum, PartialEq)] pub enum FrontendTemplate { @@ -32,7 +34,7 @@ pub enum FrontendTemplate { pub struct Cmd { pub project_path: String, - #[arg(short, long, num_args = 1.., value_parser=possible_example_values(), long_help=with_example_help())] + #[arg(short, long, num_args = 1.., value_parser=possible_example_values(), long_help=WITH_EXAMPLE_LONG_HELP_TEXT)] pub with_example: Vec, #[arg( @@ -50,14 +52,6 @@ fn possible_example_values() -> ValueParser { parser.into() } -fn with_example_help() -> String { - if check_internet_connection() { - "An optional flag to specify Soroban example contracts to include. A hello-world contract will be included by default.".to_owned() - } else { - "⚠️ Failed to fetch additional example contracts from soroban-examples repo. You can continue with initializing - the default hello_world contract will still be included".to_owned() - } -} - #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Io error: {0}")] @@ -77,9 +71,6 @@ pub enum Error { #[error("Failed to parse toml file: {0}")] TomlParseError(#[from] TomlError), - #[error("Failed to complete get request")] - UreqError(#[from] Box), - #[error("Failed to parse package.json file: {0}")] JsonParseError(#[from] JsonError), From 010b4aaaacf97d94f6e0e2625806a39899384937 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:18:18 -0500 Subject: [PATCH 3/8] Pull setting example contracts into a build_helper mod --- cmd/soroban-cli/build.rs | 92 ++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index 69efe8f2e..961bc93ff 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -1,56 +1,66 @@ fn main() { crate_git_revision::init(); - let w = &mut std::io::stdout(); - __set_example_contracts(w).unwrap(); + build_helper::set_example_contracts(); } -fn __set_example_contracts(w: &mut impl std::io::Write) -> std::io::Result<()> { - let example_contracts = get_example_contracts().unwrap().join(","); - writeln!(w, "cargo:rustc-env=EXAMPLE_CONTRACTS={example_contracts}")?; - Ok(()) -} +mod build_helper { + const GITHUB_API_URL: &str = + "https://api.github.com/repos/stellar/soroban-examples/git/trees/main?recursive=1"; -const GITHUB_API_URL: &str = - "https://api.github.com/repos/stellar/soroban-examples/git/trees/main?recursive=1"; + pub fn set_example_contracts() { + let example_contracts = get_example_contracts().unwrap().join(","); -#[derive(serde::Deserialize, Debug)] -struct RepoPath { - path: String, - #[serde(rename = "type")] - type_field: String, -} + let w = &mut std::io::stdout(); + __set_example_contracts_env_var(w, example_contracts).unwrap(); + } -#[derive(serde::Deserialize, Debug)] -struct ReqBody { - tree: Vec, -} + #[derive(serde::Deserialize, Debug)] + struct RepoPath { + path: String, + #[serde(rename = "type")] + type_field: String, + } -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Failed to complete get request")] - UreqError(#[from] Box), + #[derive(serde::Deserialize, Debug)] + struct ReqBody { + tree: Vec, + } - #[error("Io error: {0}")] - IoError(#[from] std::io::Error), -} + #[derive(thiserror::Error, Debug)] + pub enum Error { + #[error("Failed to complete get request")] + UreqError(#[from] Box), + + #[error("Io error: {0}")] + IoError(#[from] std::io::Error), + } -fn get_example_contracts() -> Result, Error> { - let body: ReqBody = ureq::get(GITHUB_API_URL) - .call() - .map_err(Box::new)? - .into_json()?; - let mut valid_examples = Vec::new(); - for item in body.tree { - if item.type_field == "blob" - || item.path.starts_with('.') - || item.path.contains('/') - || item.path == "hello_world" - { - continue; + fn get_example_contracts() -> Result, Error> { + let body: ReqBody = ureq::get(GITHUB_API_URL) + .call() + .map_err(Box::new)? + .into_json()?; + let mut valid_examples = Vec::new(); + for item in body.tree { + if item.type_field == "blob" + || item.path.starts_with('.') + || item.path.contains('/') + || item.path == "hello_world" + { + continue; + } + + valid_examples.push(item.path); } - valid_examples.push(item.path); + Ok(valid_examples) } - Ok(valid_examples) + fn __set_example_contracts_env_var( + w: &mut impl std::io::Write, + example_contracts: String, + ) -> std::io::Result<()> { + writeln!(w, "cargo:rustc-env=EXAMPLE_CONTRACTS={example_contracts}")?; + Ok(()) + } } From 24f80d2a0c695fac4fe7dcb45540c260094b54ee Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:45:34 -0500 Subject: [PATCH 4/8] Cache example contracts in target dir --- cmd/soroban-cli/build.rs | 69 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index 961bc93ff..4dd731fbe 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -4,14 +4,19 @@ fn main() { } mod build_helper { + use std::{ + fs::{metadata, File}, + io::{self, Write}, + path::{Path, PathBuf}, + }; + const GITHUB_API_URL: &str = "https://api.github.com/repos/stellar/soroban-examples/git/trees/main?recursive=1"; pub fn set_example_contracts() { - let example_contracts = get_example_contracts().unwrap().join(","); - + let example_contracts = get_example_contracts().unwrap(); let w = &mut std::io::stdout(); - __set_example_contracts_env_var(w, example_contracts).unwrap(); + set_example_contracts_env_var(w, example_contracts).unwrap(); } #[derive(serde::Deserialize, Debug)] @@ -35,7 +40,27 @@ mod build_helper { IoError(#[from] std::io::Error), } - fn get_example_contracts() -> Result, Error> { + fn get_example_contracts() -> Result { + if file_exists(cached_example_contracts_file_path().to_str().unwrap()) { + let example_contracts = std::fs::read_to_string(cached_example_contracts_file_path())?; + return Ok(example_contracts); + } + + fetch_and_cache_example_contracts() + } + + fn fetch_and_cache_example_contracts() -> Result { + let example_contracts = fetch_example_contracts().unwrap().join(","); + let cached_example_contracts = target_dir().join("example_contracts.txt"); + + if let Err(err) = write_cache(&cached_example_contracts, &example_contracts) { + eprintln!("Error writing cache: {}", err); + } + + Ok(example_contracts) + } + + fn fetch_example_contracts() -> Result, Error> { let body: ReqBody = ureq::get(GITHUB_API_URL) .call() .map_err(Box::new)? @@ -56,11 +81,45 @@ mod build_helper { Ok(valid_examples) } - fn __set_example_contracts_env_var( + fn set_example_contracts_env_var( w: &mut impl std::io::Write, example_contracts: String, ) -> std::io::Result<()> { writeln!(w, "cargo:rustc-env=EXAMPLE_CONTRACTS={example_contracts}")?; Ok(()) } + + fn cached_example_contracts_file_path() -> PathBuf { + target_dir().join("example_contracts.txt") + } + + fn target_dir() -> PathBuf { + project_root().join("target") + } + + fn project_root() -> PathBuf { + Path::new(&env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .to_path_buf() + } + + fn write_cache(cache_file_path: &Path, data: &str) -> io::Result<()> { + // Create or open the cache file + let mut file = File::create(cache_file_path)?; + + // Write the data to the cache file + file.write_all(data.as_bytes())?; + + Ok(()) + } + + fn file_exists(file_path: &str) -> bool { + if let Ok(metadata) = metadata(file_path) { + metadata.is_file() + } else { + false + } + } } From c68cf996050bfae37e3bcba47b36a197c69df713 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:58:24 -0500 Subject: [PATCH 5/8] Apply suggestions from code review Co-authored-by: Willem Wyndham --- cmd/soroban-cli/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index 4dd731fbe..065be9021 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -41,7 +41,7 @@ mod build_helper { } fn get_example_contracts() -> Result { - if file_exists(cached_example_contracts_file_path().to_str().unwrap()) { + if file_exists(&cached_example_contracts_file_path().unwrap()) { let example_contracts = std::fs::read_to_string(cached_example_contracts_file_path())?; return Ok(example_contracts); } @@ -115,7 +115,7 @@ mod build_helper { Ok(()) } - fn file_exists(file_path: &str) -> bool { + fn file_exists(file_path: &Path) -> bool { if let Ok(metadata) = metadata(file_path) { metadata.is_file() } else { From 701cdcbdaafd0f8037226a3c2a08d82172a38532 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:05:10 -0500 Subject: [PATCH 6/8] Rewrite file_exists fn Co-authored-by: Willem Wyndham --- cmd/soroban-cli/build.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index 065be9021..993ad2f05 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -5,7 +5,7 @@ fn main() { mod build_helper { use std::{ - fs::{metadata, File}, + fs::{metadata, File, Metadata}, io::{self, Write}, path::{Path, PathBuf}, }; @@ -41,7 +41,7 @@ mod build_helper { } fn get_example_contracts() -> Result { - if file_exists(&cached_example_contracts_file_path().unwrap()) { + if file_exists(&cached_example_contracts_file_path()) { let example_contracts = std::fs::read_to_string(cached_example_contracts_file_path())?; return Ok(example_contracts); } @@ -116,10 +116,9 @@ mod build_helper { } fn file_exists(file_path: &Path) -> bool { - if let Ok(metadata) = metadata(file_path) { - metadata.is_file() - } else { - false - } + metadata(file_path) + .as_ref() + .map(Metadata::is_file) + .unwrap_or(false) } } From fe69a47f7a00e1f2161ac767526f6c67dd3a547d Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:07:13 -0500 Subject: [PATCH 7/8] Rewrite file_exists fn in init.rs --- cmd/soroban-cli/src/commands/contract/init.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 258b950e2..8b589b747 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -1,7 +1,7 @@ use std::{ env, ffi::OsStr, - fs::{copy, create_dir_all, metadata, read_dir, read_to_string, write}, + fs::{copy, create_dir_all, metadata, read_dir, read_to_string, write, Metadata}, io, num::NonZeroU32, path::Path, @@ -148,7 +148,7 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { for item in TemplateFiles::iter() { let mut to = project_path.join(item.as_ref()); - if file_exists(&to.to_string_lossy()) { + if file_exists(&to) { println!( "ℹ️ Skipped creating {} as it already exists", &to.to_string_lossy() @@ -214,7 +214,7 @@ fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { })?; copy_contents(&path, &new_path)?; } else { - if file_exists(&new_path.to_string_lossy()) { + if file_exists(&new_path) { //if file is .gitignore, overwrite the file with a new .gitignore file if path.to_string_lossy().contains(".gitignore") { copy(&path, &new_path).map_err(|e| { @@ -251,12 +251,11 @@ fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { Ok(()) } -fn file_exists(file_path: &str) -> bool { - if let Ok(metadata) = metadata(file_path) { - metadata.is_file() - } else { - false - } +fn file_exists(file_path: &Path) -> bool { + metadata(file_path) + .as_ref() + .map(Metadata::is_file) + .unwrap_or(false) } fn include_example_contracts(contracts: &[String]) -> bool { From 36332c4986f5c19054d163c97613b1a58a10b686 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:10:33 -0500 Subject: [PATCH 8/8] Clippy updates --- cmd/soroban-cli/build.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs index 993ad2f05..82e17a105 100644 --- a/cmd/soroban-cli/build.rs +++ b/cmd/soroban-cli/build.rs @@ -16,7 +16,7 @@ mod build_helper { pub fn set_example_contracts() { let example_contracts = get_example_contracts().unwrap(); let w = &mut std::io::stdout(); - set_example_contracts_env_var(w, example_contracts).unwrap(); + set_example_contracts_env_var(w, &example_contracts).unwrap(); } #[derive(serde::Deserialize, Debug)] @@ -46,18 +46,18 @@ mod build_helper { return Ok(example_contracts); } - fetch_and_cache_example_contracts() + Ok(fetch_and_cache_example_contracts()) } - fn fetch_and_cache_example_contracts() -> Result { + fn fetch_and_cache_example_contracts() -> String { let example_contracts = fetch_example_contracts().unwrap().join(","); let cached_example_contracts = target_dir().join("example_contracts.txt"); if let Err(err) = write_cache(&cached_example_contracts, &example_contracts) { - eprintln!("Error writing cache: {}", err); + eprintln!("Error writing cache: {err}"); } - Ok(example_contracts) + example_contracts } fn fetch_example_contracts() -> Result, Error> { @@ -83,7 +83,7 @@ mod build_helper { fn set_example_contracts_env_var( w: &mut impl std::io::Write, - example_contracts: String, + example_contracts: &str, ) -> std::io::Result<()> { writeln!(w, "cargo:rustc-env=EXAMPLE_CONTRACTS={example_contracts}")?; Ok(())