stellar_scaffold_cli/commands/build/
mod.rs1#![allow(clippy::struct_excessive_bools)]
2use std::{fmt::Debug, io, path::Path, process::ExitStatus};
3
4use cargo_metadata::{Metadata, MetadataCommand, Package};
5use clap::Parser;
6use stellar_cli::commands::{contract::build, global};
7
8use clients::ScaffoldEnv;
9
10pub mod clients;
11pub mod docker;
12pub mod env_toml;
13
14#[derive(Parser, Debug, Clone)]
24pub struct Command {
25 #[arg(long, visible_alias = "ls")]
27 pub list: bool,
28 #[command(flatten)]
29 pub build: build::Cmd,
30 #[arg(long)]
32 pub build_clients: bool,
33 #[command(flatten)]
34 pub build_clients_args: clients::Args,
35}
36
37#[derive(thiserror::Error, Debug)]
38pub enum Error {
39 #[error(transparent)]
40 Metadata(#[from] cargo_metadata::Error),
41 #[error(transparent)]
42 EnvironmentsToml(#[from] env_toml::Error),
43 #[error(transparent)]
44 CargoCmd(io::Error),
45 #[error("exit status {0}")]
46 Exit(ExitStatus),
47 #[error("package {package} not found")]
48 PackageNotFound { package: String },
49 #[error("creating out directory: {0}")]
50 CreatingOutDir(io::Error),
51 #[error("copying wasm file: {0}")]
52 CopyingWasmFile(io::Error),
53 #[error("getting the current directory: {0}")]
54 GettingCurrentDir(io::Error),
55 #[error(transparent)]
56 StellarBuild(#[from] stellar_build::deps::Error),
57 #[error(transparent)]
58 BuildClients(#[from] clients::Error),
59 #[error(transparent)]
60 Build(#[from] build::Error),
61 #[error("Failed to start docker container")]
62 DockerStart,
63}
64
65impl Command {
66 pub fn list_packages(&self, metadata: &Metadata) -> Result<Vec<Package>, Error> {
67 let packages = self.packages(metadata)?;
68 Ok(stellar_build::deps::get_workspace(&packages)?)
69 }
70
71 async fn start_local_docker_if_needed(
72 &self,
73 workspace_root: &Path,
74 env: &ScaffoldEnv,
75 ) -> Result<(), Error> {
76 if let Some(current_env) = env_toml::Environment::get(workspace_root, &env.to_string())? {
77 if current_env.network.run_locally {
78 eprintln!("Starting local Stellar Docker container...");
79 docker::start_local_stellar().await.map_err(|e| {
80 eprintln!("Failed to start Stellar Docker container: {e:?}");
81 Error::DockerStart
82 })?;
83 eprintln!("Local Stellar network is healthy and running.");
84 }
85 }
86 Ok(())
87 }
88
89 pub async fn run(&self) -> Result<(), Error> {
90 let metadata = self.metadata()?;
91 let packages = self.list_packages(&metadata)?;
92 let workspace_root = metadata.workspace_root.as_std_path();
93
94 if let Some(env) = &self.build_clients_args.env {
95 if env == &ScaffoldEnv::Development {
96 self.start_local_docker_if_needed(workspace_root, env)
97 .await?;
98 }
99 }
100
101 if self.list {
102 for p in packages {
103 println!("{}", p.name);
104 }
105 return Ok(());
106 }
107
108 let target_dir = &metadata.target_directory;
109
110 let global_args = global::Args::default();
111
112 for p in &packages {
113 let mut cmd = self.build.clone();
114 cmd.out_dir = cmd.out_dir.or_else(|| {
115 Some(stellar_build::deps::stellar_wasm_out_dir(
116 target_dir.as_std_path(),
117 ))
118 });
119 cmd.package = Some(p.name.clone());
120 if !p.name.is_empty() {
121 cmd.meta.push(("name".to_string(), p.name.clone()));
122 }
123 if !p.version.to_string().is_empty() {
124 cmd.meta.push(("binver".to_string(), p.version.to_string()));
125 }
126 if p.homepage.is_some() {
127 cmd.meta
128 .push(("home_domain".to_string(), p.homepage.clone().unwrap()));
129 }
130 if !p.authors.is_empty() {
131 cmd.meta.push(("authors".to_string(), p.authors.join(", ")));
132 }
133 if p.repository.is_some() {
134 cmd.meta
135 .push(("source_repo".to_string(), p.repository.clone().unwrap()));
136 }
137 cmd.run(&global_args)?;
138 }
139
140 if self.build_clients {
141 let mut build_clients_args = self.build_clients_args.clone();
142 build_clients_args.workspace_root = Some(metadata.workspace_root.into_std_path_buf());
143 build_clients_args
144 .run(packages.iter().map(|p| p.name.replace('-', "_")).collect())
145 .await?;
146 }
147
148 Ok(())
149 }
150
151 fn packages(&self, metadata: &Metadata) -> Result<Vec<Package>, Error> {
152 if let Some(package) = &self.build.package {
153 let package = metadata
154 .packages
155 .iter()
156 .find(|p| p.name == *package)
157 .ok_or_else(|| Error::PackageNotFound {
158 package: package.clone(),
159 })?
160 .clone();
161 let manifest_path = package.manifest_path.clone().into_std_path_buf();
162 let mut contracts = stellar_build::deps::contract(&manifest_path)?;
163 contracts.push(package);
164 return Ok(contracts);
165 }
166 Ok(metadata
167 .packages
168 .iter()
169 .filter(|p| {
170 p.targets
172 .iter()
173 .any(|t| t.crate_types.iter().any(|c| c == "cdylib"))
174 })
175 .cloned()
176 .collect())
177 }
178
179 pub(crate) fn metadata(&self) -> Result<Metadata, cargo_metadata::Error> {
180 let mut cmd = MetadataCommand::new();
181 cmd.no_deps();
182 if let Some(manifest_path) = &self.build.manifest_path {
186 cmd.manifest_path(manifest_path);
187 }
188 cmd.exec()
192 }
193}