diff --git a/crates/cli/src/cli/mod.rs b/crates/cli/src/cli/mod.rs index 4060d318..5c00f83e 100644 --- a/crates/cli/src/cli/mod.rs +++ b/crates/cli/src/cli/mod.rs @@ -189,9 +189,12 @@ pub struct StartSimnet { /// List of keypair paths to airdrop (eg. surfpool start --airdrop-keypair-path ~/.config/solana/id.json --airdrop-keypair-path ~/.config/solana/id2.json) #[arg(long = "airdrop-keypair-path", short = 'k', default_value = DEFAULT_SOLANA_KEYPAIR_PATH.as_str())] pub airdrop_keypair_path: Vec, - /// Watch programs in your `target/deploy` folder, and automatically re-execute the deployment runbook when the `.so` files change. (eg. surfpool start --watch) + /// Watch programs in your artifacts folder (default: `target/deploy`), and automatically re-execute the deployment runbook when the `.so` files change. (eg. surfpool start --watch) #[clap(long = "watch", action=ArgAction::SetTrue, default_value = "false")] pub watch: bool, + /// Override the path where .so program artifacts are loaded from (default: target/deploy). (eg. surfpool start --artifacts-path ./target/deploy/debug) + #[arg(long = "artifacts-path")] + pub artifacts_path: Option, /// List of geyser plugins to load (eg. surfpool start --geyser-plugin-config plugin1.json --geyser-plugin-config plugin2.json) #[arg(long = "geyser-plugin-config", short = 'g')] pub plugin_config_path: Vec, diff --git a/crates/cli/src/cli/simnet/mod.rs b/crates/cli/src/cli/simnet/mod.rs index e0c69848..0e7b1f2d 100644 --- a/crates/cli/src/cli/simnet/mod.rs +++ b/crates/cli/src/cli/simnet/mod.rs @@ -512,15 +512,22 @@ async fn write_and_execute_iac( // If there were existing on-disk runbooks, we'll execute those instead of in-memory ones // If there were no existing runbooks and the user requested autopilot, we'll generate and execute in-memory runbooks // If there were no existing runbooks and the user did not request autopilot, we'll generate and execute on-disk runbooks - let do_execute_in_memory_runbooks = cmd.anchor_compat && !txtx_manifest_exists; - if !cmd.anchor_compat && txtx_manifest_exists { + // When --artifacts-path is set, always use in-memory runbooks so the custom bin_path is injected + let has_custom_artifacts_path = cmd.artifacts_path.is_some(); + let do_execute_in_memory_runbooks = + has_custom_artifacts_path || (cmd.anchor_compat && !txtx_manifest_exists); + if !has_custom_artifacts_path && !cmd.anchor_compat && txtx_manifest_exists { let runbooks_ids_to_execute = cmd.runbooks.clone(); on_disk_runbook_data = Some((txtx_manifest_location.clone(), runbooks_ids_to_execute)); }; // Are we in a project directory? - if let Ok(deployment) = - detect_program_frameworks(&cmd.manifest_path, &cmd.anchor_test_config_paths).await + if let Ok(deployment) = detect_program_frameworks( + &cmd.manifest_path, + &cmd.anchor_test_config_paths, + cmd.artifacts_path.as_deref(), + ) + .await { if let Some(ProgramFrameworkData { framework, @@ -549,7 +556,8 @@ async fn write_and_execute_iac( } // Is infrastructure-as-code (IaC) already setup? - let do_write_scaffold = !cmd.anchor_compat && !txtx_manifest_exists; + let do_write_scaffold = + !has_custom_artifacts_path && !cmd.anchor_compat && !txtx_manifest_exists; if do_write_scaffold { // Scaffold IaC scaffold_iac_layout( @@ -572,6 +580,7 @@ async fn write_and_execute_iac( &accounts, &accounts_dir, generate_subgraphs, + cmd.artifacts_path.as_deref(), )?); } } @@ -593,11 +602,16 @@ async fn write_and_execute_iac( .map_err(|e| format!("Thread to execute runbooks exited: {}", e))?; if cmd.watch { + let artifacts_path_for_watch = cmd.artifacts_path.clone(); let _handle = hiro_system_kit::thread_named("Watch Filesystem") .spawn(move || { let mut target_path = base_location.clone(); - let _ = target_path.append_path("target"); - let _ = target_path.append_path("deploy"); + if let Some(ref path) = artifacts_path_for_watch { + let _ = target_path.append_path(path); + } else { + let _ = target_path.append_path("target"); + let _ = target_path.append_path("deploy"); + } let (tx, rx) = mpsc::channel::>(); let mut watcher = notify::recommended_watcher(tx).map_err(|e| e.to_string())?; watcher diff --git a/crates/cli/src/scaffold/anchor.rs b/crates/cli/src/scaffold/anchor.rs index 6c6ffa68..7b0bd48e 100644 --- a/crates/cli/src/scaffold/anchor.rs +++ b/crates/cli/src/scaffold/anchor.rs @@ -44,6 +44,7 @@ fn should_generate_subgraphs(anchor_version: &Option) -> bool { pub fn try_get_programs_from_project( base_location: FileLocation, test_suite_paths: &[String], + artifacts_path: Option<&str>, ) -> Result, String> { let mut manifest_location = base_location.clone(); manifest_location.append_path("Anchor.toml")?; @@ -60,10 +61,16 @@ pub fn try_get_programs_from_project( if let Some((_, deployments)) = manifest.programs.iter().next() { for (program_name, deployment) in deployments.iter() { let so_exists = { - let mut so_path = target_location.clone(); - so_path.append_path("deploy")?; - so_path.append_path(&format!("{}.so", program_name))?; - so_path.exists() + if let Some(artifacts) = artifacts_path { + let mut so_path = base_location.clone(); + so_path.append_path(&format!("{}/{}.so", artifacts, program_name))?; + so_path.exists() + } else { + let mut so_path = target_location.clone(); + so_path.append_path("deploy")?; + so_path.append_path(&format!("{}.so", program_name))?; + so_path.exists() + } }; programs.push(ProgramMetadata::new( program_name, diff --git a/crates/cli/src/scaffold/mod.rs b/crates/cli/src/scaffold/mod.rs index a89760e4..bb2dc86d 100644 --- a/crates/cli/src/scaffold/mod.rs +++ b/crates/cli/src/scaffold/mod.rs @@ -76,20 +76,23 @@ impl ProgramFrameworkData { pub async fn detect_program_frameworks( manifest_path: &str, test_paths: &[String], + artifacts_path: Option<&str>, ) -> Result, String> { let manifest_location = FileLocation::from_path_string(manifest_path)?; let base_dir = manifest_location.get_parent_location()?; // Look for Anchor project layout // Note: Poseidon projects generate Anchor.toml files, so they will also be identified here - if let Some(res) = anchor::try_get_programs_from_project(base_dir.clone(), test_paths) - .map_err(|e| format!("Invalid Anchor project: {e}"))? + if let Some(res) = + anchor::try_get_programs_from_project(base_dir.clone(), test_paths, artifacts_path) + .map_err(|e| format!("Invalid Anchor project: {e}"))? { return Ok(Some(res)); } // Look for Steel project layout - if let Some((framework, programs)) = steel::try_get_programs_from_project(base_dir.clone()) - .map_err(|e| format!("Invalid Steel project: {e}"))? + if let Some((framework, programs)) = + steel::try_get_programs_from_project(base_dir.clone(), artifacts_path) + .map_err(|e| format!("Invalid Steel project: {e}"))? { return Ok(Some(ProgramFrameworkData::partial(framework, programs))); } @@ -102,15 +105,17 @@ pub async fn detect_program_frameworks( } // Look for Pinocchio project layout - if let Some((framework, programs)) = pinocchio::try_get_programs_from_project(base_dir.clone()) - .map_err(|e| format!("Invalid Pinocchio project: {e}"))? + if let Some((framework, programs)) = + pinocchio::try_get_programs_from_project(base_dir.clone(), artifacts_path) + .map_err(|e| format!("Invalid Pinocchio project: {e}"))? { return Ok(Some(ProgramFrameworkData::partial(framework, programs))); } // Look for Native project layout - if let Some((framework, programs)) = native::try_get_programs_from_project(base_dir.clone()) - .map_err(|e| format!("Invalid Native project: {e}"))? + if let Some((framework, programs)) = + native::try_get_programs_from_project(base_dir.clone(), artifacts_path) + .map_err(|e| format!("Invalid Native project: {e}"))? { return Ok(Some(ProgramFrameworkData::partial(framework, programs))); } @@ -142,6 +147,7 @@ pub fn scaffold_in_memory_iac( accounts: &Option>, accounts_dir: &Option>, generate_subgraphs: bool, + artifacts_path: Option<&str>, ) -> Result<(String, RunbookSources, WorkspaceManifest), String> { let mut deployment_runbook_src: String = String::new(); @@ -157,8 +163,10 @@ pub fn scaffold_in_memory_iac( for program_metadata in programs.iter() { if program_metadata.so_exists { deployment_runbook_src.push_str( - &framework - .get_in_memory_interpolated_program_deployment_template(&program_metadata.name), + &framework.get_in_memory_interpolated_program_deployment_template( + &program_metadata.name, + artifacts_path, + ), ); if generate_subgraphs { diff --git a/crates/cli/src/scaffold/native.rs b/crates/cli/src/scaffold/native.rs index abf45564..15402c1b 100644 --- a/crates/cli/src/scaffold/native.rs +++ b/crates/cli/src/scaffold/native.rs @@ -13,6 +13,7 @@ use crate::types::Framework; /// it is considered a native project. pub fn try_get_programs_from_project( base_location: FileLocation, + artifacts_path: Option<&str>, ) -> Result)>> { let mut manifest_location = base_location.clone(); manifest_location @@ -29,6 +30,7 @@ pub fn try_get_programs_from_project( "solana-program", &base_location, &manifest, + artifacts_path, )? else { return Ok(None); diff --git a/crates/cli/src/scaffold/pinocchio.rs b/crates/cli/src/scaffold/pinocchio.rs index cf1634d0..847af94c 100644 --- a/crates/cli/src/scaffold/pinocchio.rs +++ b/crates/cli/src/scaffold/pinocchio.rs @@ -13,6 +13,7 @@ use crate::types::Framework; /// it is considered a native project. pub fn try_get_programs_from_project( base_location: FileLocation, + artifacts_path: Option<&str>, ) -> Result)>> { let mut manifest_location = base_location.clone(); manifest_location @@ -25,8 +26,12 @@ pub fn try_get_programs_from_project( let manifest = CargoManifestFile::from_manifest_str(&manifest) .map_err(|e| anyhow!("unable to read Cargo.toml: {}", e))?; - let Some(program_metadata) = - get_program_metadata_from_manifest_with_dep("pinocchio", &base_location, &manifest)? + let Some(program_metadata) = get_program_metadata_from_manifest_with_dep( + "pinocchio", + &base_location, + &manifest, + artifacts_path, + )? else { return Ok(None); }; diff --git a/crates/cli/src/scaffold/steel.rs b/crates/cli/src/scaffold/steel.rs index bfe6001a..55c822d9 100644 --- a/crates/cli/src/scaffold/steel.rs +++ b/crates/cli/src/scaffold/steel.rs @@ -13,6 +13,7 @@ use crate::types::Framework; /// it is considered a native project. pub fn try_get_programs_from_project( base_location: FileLocation, + artifacts_path: Option<&str>, ) -> Result)>> { let mut manifest_location = base_location.clone(); manifest_location @@ -25,8 +26,12 @@ pub fn try_get_programs_from_project( let manifest = CargoManifestFile::from_manifest_str(&manifest) .map_err(|e| anyhow!("unable to read Cargo.toml: {}", e))?; - let Some(program_metadata) = - get_program_metadata_from_manifest_with_dep("steel", &base_location, &manifest)? + let Some(program_metadata) = get_program_metadata_from_manifest_with_dep( + "steel", + &base_location, + &manifest, + artifacts_path, + )? else { return Ok(None); }; diff --git a/crates/cli/src/scaffold/utils.rs b/crates/cli/src/scaffold/utils.rs index 446282db..bdbda590 100644 --- a/crates/cli/src/scaffold/utils.rs +++ b/crates/cli/src/scaffold/utils.rs @@ -12,6 +12,7 @@ pub fn get_program_metadata_from_manifest_with_dep( dependency_indicator: &str, base_location: &FileLocation, manifest: &CargoManifestFile, + artifacts_path: Option<&str>, ) -> Result> { let Some(manifest) = manifest.get_manifest_with_dependency(dependency_indicator, base_location)? @@ -43,12 +44,15 @@ pub fn get_program_metadata_from_manifest_with_dep( }; let so_exists = { + let so_path_str = if let Some(artifacts) = artifacts_path { + format!("{}/{}.so", artifacts, program_name) + } else { + format!("target/deploy/{}.so", program_name) + }; let mut so_path = base_location.clone(); - so_path - .append_path(&format!("target/deploy/{}.so", program_name)) - .map_err(|e| { - anyhow!("failed to construct path to program .so file for existence check: {e}") - })?; + so_path.append_path(&so_path_str).map_err(|e| { + anyhow!("failed to construct path to program .so file for existence check: {e}") + })?; so_path.exists() }; diff --git a/crates/cli/src/types/mod.rs b/crates/cli/src/types/mod.rs index aae397ba..e6bf2b0d 100644 --- a/crates/cli/src/types/mod.rs +++ b/crates/cli/src/types/mod.rs @@ -28,8 +28,9 @@ impl Framework { pub fn get_in_memory_interpolated_program_deployment_template( &self, program_name: &str, + artifacts_path: Option<&str>, ) -> String { - match self { + let base = match self { Framework::Anchor => { get_in_memory_interpolated_anchor_program_deployment_template(program_name) } @@ -37,6 +38,22 @@ impl Framework { Framework::Native | Framework::Steel | Framework::Pinocchio => { get_in_memory_interpolated_native_program_deployment_template(program_name) } + }; + if let Some(artifacts) = artifacts_path { + let get_program_fn = match self { + Framework::Anchor => "svm::get_program_from_anchor_project", + _ => "svm::get_program_from_native_project", + }; + let bin_path = format!("{}/{}.so", artifacts.trim_end_matches('/'), program_name); + // Pass bin_path as the 4th argument (program_name, keypair_path, idl_path, bin_path) + let old = format!("program = {}(\"{}\") ", get_program_fn, program_name); + let new = format!( + "program = {}(\"{}\", null, null, \"{}\") ", + get_program_fn, program_name, bin_path + ); + base.replace(&old, &new) + } else { + base } } pub fn get_interpolated_subgraph_template(