Skip to content

Commit dd0274a

Browse files
authored
feat(cli): add --artifacts-path flag to override .so program location (#573)
1 parent 2129ad9 commit dd0274a

File tree

9 files changed

+102
-32
lines changed

9 files changed

+102
-32
lines changed

crates/cli/src/cli/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,12 @@ pub struct StartSimnet {
189189
/// List of keypair paths to airdrop (eg. surfpool start --airdrop-keypair-path ~/.config/solana/id.json --airdrop-keypair-path ~/.config/solana/id2.json)
190190
#[arg(long = "airdrop-keypair-path", short = 'k', default_value = DEFAULT_SOLANA_KEYPAIR_PATH.as_str())]
191191
pub airdrop_keypair_path: Vec<String>,
192-
/// Watch programs in your `target/deploy` folder, and automatically re-execute the deployment runbook when the `.so` files change. (eg. surfpool start --watch)
192+
/// 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)
193193
#[clap(long = "watch", action=ArgAction::SetTrue, default_value = "false")]
194194
pub watch: bool,
195+
/// Override the path where .so program artifacts are loaded from (default: target/deploy). (eg. surfpool start --artifacts-path ./target/deploy/debug)
196+
#[arg(long = "artifacts-path")]
197+
pub artifacts_path: Option<String>,
195198
/// List of geyser plugins to load (eg. surfpool start --geyser-plugin-config plugin1.json --geyser-plugin-config plugin2.json)
196199
#[arg(long = "geyser-plugin-config", short = 'g')]
197200
pub plugin_config_path: Vec<String>,

crates/cli/src/cli/simnet/mod.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -512,15 +512,22 @@ async fn write_and_execute_iac(
512512
// If there were existing on-disk runbooks, we'll execute those instead of in-memory ones
513513
// If there were no existing runbooks and the user requested autopilot, we'll generate and execute in-memory runbooks
514514
// If there were no existing runbooks and the user did not request autopilot, we'll generate and execute on-disk runbooks
515-
let do_execute_in_memory_runbooks = cmd.anchor_compat && !txtx_manifest_exists;
516-
if !cmd.anchor_compat && txtx_manifest_exists {
515+
// When --artifacts-path is set, always use in-memory runbooks so the custom bin_path is injected
516+
let has_custom_artifacts_path = cmd.artifacts_path.is_some();
517+
let do_execute_in_memory_runbooks =
518+
has_custom_artifacts_path || (cmd.anchor_compat && !txtx_manifest_exists);
519+
if !has_custom_artifacts_path && !cmd.anchor_compat && txtx_manifest_exists {
517520
let runbooks_ids_to_execute = cmd.runbooks.clone();
518521
on_disk_runbook_data = Some((txtx_manifest_location.clone(), runbooks_ids_to_execute));
519522
};
520523

521524
// Are we in a project directory?
522-
if let Ok(deployment) =
523-
detect_program_frameworks(&cmd.manifest_path, &cmd.anchor_test_config_paths).await
525+
if let Ok(deployment) = detect_program_frameworks(
526+
&cmd.manifest_path,
527+
&cmd.anchor_test_config_paths,
528+
cmd.artifacts_path.as_deref(),
529+
)
530+
.await
524531
{
525532
if let Some(ProgramFrameworkData {
526533
framework,
@@ -549,7 +556,8 @@ async fn write_and_execute_iac(
549556
}
550557

551558
// Is infrastructure-as-code (IaC) already setup?
552-
let do_write_scaffold = !cmd.anchor_compat && !txtx_manifest_exists;
559+
let do_write_scaffold =
560+
!has_custom_artifacts_path && !cmd.anchor_compat && !txtx_manifest_exists;
553561
if do_write_scaffold {
554562
// Scaffold IaC
555563
scaffold_iac_layout(
@@ -572,6 +580,7 @@ async fn write_and_execute_iac(
572580
&accounts,
573581
&accounts_dir,
574582
generate_subgraphs,
583+
cmd.artifacts_path.as_deref(),
575584
)?);
576585
}
577586
}
@@ -593,11 +602,16 @@ async fn write_and_execute_iac(
593602
.map_err(|e| format!("Thread to execute runbooks exited: {}", e))?;
594603

595604
if cmd.watch {
605+
let artifacts_path_for_watch = cmd.artifacts_path.clone();
596606
let _handle = hiro_system_kit::thread_named("Watch Filesystem")
597607
.spawn(move || {
598608
let mut target_path = base_location.clone();
599-
let _ = target_path.append_path("target");
600-
let _ = target_path.append_path("deploy");
609+
if let Some(ref path) = artifacts_path_for_watch {
610+
let _ = target_path.append_path(path);
611+
} else {
612+
let _ = target_path.append_path("target");
613+
let _ = target_path.append_path("deploy");
614+
}
601615
let (tx, rx) = mpsc::channel::<NotifyResult<Event>>();
602616
let mut watcher = notify::recommended_watcher(tx).map_err(|e| e.to_string())?;
603617
watcher

crates/cli/src/scaffold/anchor.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ fn should_generate_subgraphs(anchor_version: &Option<String>) -> bool {
4444
pub fn try_get_programs_from_project(
4545
base_location: FileLocation,
4646
test_suite_paths: &[String],
47+
artifacts_path: Option<&str>,
4748
) -> Result<Option<ProgramFrameworkData>, String> {
4849
let mut manifest_location = base_location.clone();
4950
manifest_location.append_path("Anchor.toml")?;
@@ -60,10 +61,16 @@ pub fn try_get_programs_from_project(
6061
if let Some((_, deployments)) = manifest.programs.iter().next() {
6162
for (program_name, deployment) in deployments.iter() {
6263
let so_exists = {
63-
let mut so_path = target_location.clone();
64-
so_path.append_path("deploy")?;
65-
so_path.append_path(&format!("{}.so", program_name))?;
66-
so_path.exists()
64+
if let Some(artifacts) = artifacts_path {
65+
let mut so_path = base_location.clone();
66+
so_path.append_path(&format!("{}/{}.so", artifacts, program_name))?;
67+
so_path.exists()
68+
} else {
69+
let mut so_path = target_location.clone();
70+
so_path.append_path("deploy")?;
71+
so_path.append_path(&format!("{}.so", program_name))?;
72+
so_path.exists()
73+
}
6774
};
6875
programs.push(ProgramMetadata::new(
6976
program_name,

crates/cli/src/scaffold/mod.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,23 @@ impl ProgramFrameworkData {
7676
pub async fn detect_program_frameworks(
7777
manifest_path: &str,
7878
test_paths: &[String],
79+
artifacts_path: Option<&str>,
7980
) -> Result<Option<ProgramFrameworkData>, String> {
8081
let manifest_location = FileLocation::from_path_string(manifest_path)?;
8182
let base_dir = manifest_location.get_parent_location()?;
8283
// Look for Anchor project layout
8384
// Note: Poseidon projects generate Anchor.toml files, so they will also be identified here
84-
if let Some(res) = anchor::try_get_programs_from_project(base_dir.clone(), test_paths)
85-
.map_err(|e| format!("Invalid Anchor project: {e}"))?
85+
if let Some(res) =
86+
anchor::try_get_programs_from_project(base_dir.clone(), test_paths, artifacts_path)
87+
.map_err(|e| format!("Invalid Anchor project: {e}"))?
8688
{
8789
return Ok(Some(res));
8890
}
8991

9092
// Look for Steel project layout
91-
if let Some((framework, programs)) = steel::try_get_programs_from_project(base_dir.clone())
92-
.map_err(|e| format!("Invalid Steel project: {e}"))?
93+
if let Some((framework, programs)) =
94+
steel::try_get_programs_from_project(base_dir.clone(), artifacts_path)
95+
.map_err(|e| format!("Invalid Steel project: {e}"))?
9396
{
9497
return Ok(Some(ProgramFrameworkData::partial(framework, programs)));
9598
}
@@ -102,15 +105,17 @@ pub async fn detect_program_frameworks(
102105
}
103106

104107
// Look for Pinocchio project layout
105-
if let Some((framework, programs)) = pinocchio::try_get_programs_from_project(base_dir.clone())
106-
.map_err(|e| format!("Invalid Pinocchio project: {e}"))?
108+
if let Some((framework, programs)) =
109+
pinocchio::try_get_programs_from_project(base_dir.clone(), artifacts_path)
110+
.map_err(|e| format!("Invalid Pinocchio project: {e}"))?
107111
{
108112
return Ok(Some(ProgramFrameworkData::partial(framework, programs)));
109113
}
110114

111115
// Look for Native project layout
112-
if let Some((framework, programs)) = native::try_get_programs_from_project(base_dir.clone())
113-
.map_err(|e| format!("Invalid Native project: {e}"))?
116+
if let Some((framework, programs)) =
117+
native::try_get_programs_from_project(base_dir.clone(), artifacts_path)
118+
.map_err(|e| format!("Invalid Native project: {e}"))?
114119
{
115120
return Ok(Some(ProgramFrameworkData::partial(framework, programs)));
116121
}
@@ -142,6 +147,7 @@ pub fn scaffold_in_memory_iac(
142147
accounts: &Option<Vec<AccountEntry>>,
143148
accounts_dir: &Option<Vec<AccountDirEntry>>,
144149
generate_subgraphs: bool,
150+
artifacts_path: Option<&str>,
145151
) -> Result<(String, RunbookSources, WorkspaceManifest), String> {
146152
let mut deployment_runbook_src: String = String::new();
147153

@@ -157,8 +163,10 @@ pub fn scaffold_in_memory_iac(
157163
for program_metadata in programs.iter() {
158164
if program_metadata.so_exists {
159165
deployment_runbook_src.push_str(
160-
&framework
161-
.get_in_memory_interpolated_program_deployment_template(&program_metadata.name),
166+
&framework.get_in_memory_interpolated_program_deployment_template(
167+
&program_metadata.name,
168+
artifacts_path,
169+
),
162170
);
163171

164172
if generate_subgraphs {

crates/cli/src/scaffold/native.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::types::Framework;
1313
/// it is considered a native project.
1414
pub fn try_get_programs_from_project(
1515
base_location: FileLocation,
16+
artifacts_path: Option<&str>,
1617
) -> Result<Option<(Framework, Vec<ProgramMetadata>)>> {
1718
let mut manifest_location = base_location.clone();
1819
manifest_location
@@ -29,6 +30,7 @@ pub fn try_get_programs_from_project(
2930
"solana-program",
3031
&base_location,
3132
&manifest,
33+
artifacts_path,
3234
)?
3335
else {
3436
return Ok(None);

crates/cli/src/scaffold/pinocchio.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::types::Framework;
1313
/// it is considered a native project.
1414
pub fn try_get_programs_from_project(
1515
base_location: FileLocation,
16+
artifacts_path: Option<&str>,
1617
) -> Result<Option<(Framework, Vec<ProgramMetadata>)>> {
1718
let mut manifest_location = base_location.clone();
1819
manifest_location
@@ -25,8 +26,12 @@ pub fn try_get_programs_from_project(
2526
let manifest = CargoManifestFile::from_manifest_str(&manifest)
2627
.map_err(|e| anyhow!("unable to read Cargo.toml: {}", e))?;
2728

28-
let Some(program_metadata) =
29-
get_program_metadata_from_manifest_with_dep("pinocchio", &base_location, &manifest)?
29+
let Some(program_metadata) = get_program_metadata_from_manifest_with_dep(
30+
"pinocchio",
31+
&base_location,
32+
&manifest,
33+
artifacts_path,
34+
)?
3035
else {
3136
return Ok(None);
3237
};

crates/cli/src/scaffold/steel.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::types::Framework;
1313
/// it is considered a native project.
1414
pub fn try_get_programs_from_project(
1515
base_location: FileLocation,
16+
artifacts_path: Option<&str>,
1617
) -> Result<Option<(Framework, Vec<ProgramMetadata>)>> {
1718
let mut manifest_location = base_location.clone();
1819
manifest_location
@@ -25,8 +26,12 @@ pub fn try_get_programs_from_project(
2526
let manifest = CargoManifestFile::from_manifest_str(&manifest)
2627
.map_err(|e| anyhow!("unable to read Cargo.toml: {}", e))?;
2728

28-
let Some(program_metadata) =
29-
get_program_metadata_from_manifest_with_dep("steel", &base_location, &manifest)?
29+
let Some(program_metadata) = get_program_metadata_from_manifest_with_dep(
30+
"steel",
31+
&base_location,
32+
&manifest,
33+
artifacts_path,
34+
)?
3035
else {
3136
return Ok(None);
3237
};

crates/cli/src/scaffold/utils.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub fn get_program_metadata_from_manifest_with_dep(
1212
dependency_indicator: &str,
1313
base_location: &FileLocation,
1414
manifest: &CargoManifestFile,
15+
artifacts_path: Option<&str>,
1516
) -> Result<Option<ProgramMetadata>> {
1617
let Some(manifest) =
1718
manifest.get_manifest_with_dependency(dependency_indicator, base_location)?
@@ -43,12 +44,15 @@ pub fn get_program_metadata_from_manifest_with_dep(
4344
};
4445

4546
let so_exists = {
47+
let so_path_str = if let Some(artifacts) = artifacts_path {
48+
format!("{}/{}.so", artifacts, program_name)
49+
} else {
50+
format!("target/deploy/{}.so", program_name)
51+
};
4652
let mut so_path = base_location.clone();
47-
so_path
48-
.append_path(&format!("target/deploy/{}.so", program_name))
49-
.map_err(|e| {
50-
anyhow!("failed to construct path to program .so file for existence check: {e}")
51-
})?;
53+
so_path.append_path(&so_path_str).map_err(|e| {
54+
anyhow!("failed to construct path to program .so file for existence check: {e}")
55+
})?;
5256
so_path.exists()
5357
};
5458

crates/cli/src/types/mod.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,37 @@ impl Framework {
2828
pub fn get_in_memory_interpolated_program_deployment_template(
2929
&self,
3030
program_name: &str,
31+
artifacts_path: Option<&str>,
3132
) -> String {
32-
match self {
33+
let base = match self {
3334
Framework::Anchor => {
3435
get_in_memory_interpolated_anchor_program_deployment_template(program_name)
3536
}
3637
Framework::Typhoon => todo!(),
3738
Framework::Native | Framework::Steel | Framework::Pinocchio => {
3839
get_in_memory_interpolated_native_program_deployment_template(program_name)
3940
}
41+
};
42+
if let Some(artifacts) = artifacts_path {
43+
let get_program_fn = match self {
44+
Framework::Anchor => "svm::get_program_from_anchor_project",
45+
_ => "svm::get_program_from_native_project",
46+
};
47+
let bin_path = format!("{}/{}.so", artifacts.trim_end_matches('/'), program_name);
48+
let keypair_path = format!(
49+
"{}/{}-keypair.json",
50+
artifacts.trim_end_matches('/'),
51+
program_name
52+
);
53+
// Override keypair_path (2nd arg) and bin_path (4th arg) to use artifacts dir
54+
let old = format!("program = {}(\"{}\") ", get_program_fn, program_name);
55+
let new = format!(
56+
"program = {}(\"{}\", \"{}\", null, \"{}\") ",
57+
get_program_fn, program_name, keypair_path, bin_path
58+
);
59+
base.replace(&old, &new)
60+
} else {
61+
base
4062
}
4163
}
4264
pub fn get_interpolated_subgraph_template(

0 commit comments

Comments
 (0)