1use colored::{ColoredString, Colorize};
2use include_dir::{include_dir, Dir};
3use lazy_static::lazy_static;
4use pax_manifest::HostCrateInfo;
5use pax_runtime::api::serde::Deserialize;
6use std::fs;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use std::str::FromStr;
10use std::sync::{Arc, Mutex};
11use syn::spanned::Spanned;
12use syn::visit::Visit;
13use syn::{parse_file, ItemStruct};
14use toml_edit;
15use toml_edit::Document;
16
17lazy_static! {
18 #[allow(non_snake_case)]
19 pub static ref PAX_BADGE: ColoredString = "[Pax]".bold().on_black().white();
20 pub static ref DIR_IGNORE_LIST_MACOS : Vec<&'static str> = vec!["target", ".build", ".git", "tests"];
21 pub static ref DIR_IGNORE_LIST_WEB : Vec<&'static str> = vec![".git"];
22}
23
24pub static PAX_CREATE_TEMPLATE: Dir<'_> =
25 include_dir!("$CARGO_MANIFEST_DIR/files/new-project/new-project-template");
26pub static PAX_WEB_INTERFACE_TEMPLATE: Dir<'_> =
27 include_dir!("$CARGO_MANIFEST_DIR/files/interfaces/web/public/");
28pub static PAX_MACOS_INTERFACE_TEMPLATE: Dir<'_> =
29 include_dir!("$CARGO_MANIFEST_DIR/files/interfaces/macos/");
30pub static PAX_IOS_INTERFACE_TEMPLATE: Dir<'_> =
31 include_dir!("$CARGO_MANIFEST_DIR/files/interfaces/ios/");
32
33pub static PAX_SWIFT_CARTRIDGE_TEMPLATE: Dir<'_> =
34 include_dir!("$CARGO_MANIFEST_DIR/files/swift/pax-swift-cartridge/");
35pub static PAX_SWIFT_COMMON_TEMPLATE: Dir<'_> =
36 include_dir!("$CARGO_MANIFEST_DIR/files/swift/pax-swift-common/");
37
38pub const PAX_CREATE_LIBDEV_TEMPLATE_DIR_NAME: &str = "new-libdev-project-template";
39pub const INTERFACE_DIR_NAME: &str = "interface";
40pub const BUILD_DIR_NAME: &str = "build";
41pub const PUBLIC_DIR_NAME: &str = "public";
42pub const ASSETS_DIR_NAME: &str = "assets";
43
44pub const ERR_SPAWN: &str = "failed to spawn child";
45
46pub const ALL_PKGS: &[&str] = &[
49 "pax-chassis-common",
50 "pax-chassis-ios",
51 "pax-chassis-macos",
52 "pax-chassis-web",
53 "pax-cli",
54 "pax-compiler",
55 "pax-designtime",
56 "pax-kit",
57 "pax-runtime",
58 "pax-runtime-api",
59 "pax-engine",
60 "pax-macro",
61 "pax-message",
62 "pax-std",
63 "pax-manifest",
64 "pax-lang",
65];
66
67#[derive(Debug, Deserialize)]
68struct Metadata {
69 packages: Vec<Package>,
70}
71
72#[derive(Debug, Deserialize)]
73struct Package {
74 name: String,
75 version: String,
76}
77
78pub fn set_path_on_pax_dependencies(full_path: &Path) {
79 let mut doc = fs::read_to_string(&full_path.join("Cargo.toml"))
81 .expect("Failed to read Cargo.toml")
82 .parse::<toml_edit::Document>()
83 .expect("Failed to parse Cargo.toml");
84
85 if let Some(deps) = doc
87 .as_table_mut()
88 .entry("dependencies")
89 .or_insert_with(toml_edit::table)
90 .as_table_mut()
91 {
92 let keys: Vec<String> = deps
93 .iter()
94 .filter_map(|(key, _)| {
95 if key.starts_with("pax-") {
96 Some(key.to_string())
97 } else {
98 None
99 }
100 })
101 .collect();
102
103 for key in keys {
104 let dep_entry = deps.get_mut(&key).unwrap();
105
106 if let toml_edit::Item::Value(toml_edit::Value::InlineTable(ref mut dep_table)) =
107 dep_entry
108 {
109 dep_table.insert(
110 "path",
111 toml_edit::Value::String(toml_edit::Formatted::new(
112 ".pax/pkg/".to_string() + &key,
113 )),
114 );
115 }
116 }
117 }
118
119 fs::write(&full_path.join("Cargo.toml"), doc.to_string())
121 .expect("Failed to write modified Cargo.toml");
122}
123
124pub fn update_pax_dependency_versions(doc: &mut Document, ctx_version: &str) {
125 if let Some(deps) = doc
126 .as_table_mut()
127 .entry("dependencies")
128 .or_insert_with(toml_edit::table)
129 .as_table_mut()
130 {
131 let keys: Vec<String> = deps
132 .iter()
133 .filter_map(|(key, _)| {
134 if key.starts_with("pax-") {
135 Some(key.to_string())
136 } else {
137 None
138 }
139 })
140 .collect();
141
142 for key in keys {
143 let dep_entry = deps.get_mut(&key).unwrap();
144
145 if let toml_edit::Item::Value(toml_edit::Value::InlineTable(ref mut dep_table)) =
146 dep_entry
147 {
148 dep_table.insert(
149 "version",
150 toml_edit::Value::String(toml_edit::Formatted::new(ctx_version.to_string())),
151 );
152 } else {
153 let dep_string = format!("version = \"{}\"", ctx_version);
154 *dep_entry = toml_edit::Item::from_str(&dep_string).unwrap_or_default();
155 }
156 }
157 }
158}
159
160const ERR_LOCK: &str = "Failed to lock process_child_ids mutex";
161
162pub fn wait_with_output(
163 process_child_ids: &Arc<Mutex<Vec<u64>>>,
164 child: std::process::Child,
165) -> std::process::Output {
166 let child_id: u64 = child.id().into();
167
168 process_child_ids.lock().expect(ERR_LOCK).push(child_id);
170
171 let output = child
173 .wait_with_output()
174 .expect("Failed to wait for child process");
175
176 process_child_ids
178 .lock()
179 .expect(ERR_LOCK)
180 .retain(|&id| id != child_id);
181
182 output
183}
184
185pub fn get_or_create_pax_directory(project_path: &PathBuf) -> PathBuf {
186 let working_path = std::path::Path::new(project_path).join(".pax");
187 std::fs::create_dir_all(&working_path).unwrap();
188 fs::canonicalize(working_path).unwrap()
189}
190
191pub fn get_version_of_whitelisted_packages(path: &str) -> Result<String, &'static str> {
192 let mut cmd = Command::new("cargo");
193 let output = cmd
194 .arg("metadata")
195 .arg("--format-version=1")
196 .current_dir(path)
197 .output()
198 .expect("Failed to execute `cargo metadata`");
199
200 if !output.status.success() {
201 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
202 panic!("Failed to get metadata from Cargo");
203 }
204
205 let metadata: Metadata =
206 serde_json::from_slice(&output.stdout).expect("Failed to parse JSON from `cargo metadata`");
207
208 let mut tracked_version: Option<String> = None;
209
210 for package in &metadata.packages {
211 if ALL_PKGS.contains(&package.name.as_str()) {
212 if let Some(ref version) = tracked_version {
213 if package.version != *version {
214 panic!(
215 "Version mismatch for {}: expected {}, found {}",
216 package.name, version, package.version
217 );
218 }
219 } else {
220 tracked_version = Some(package.version.clone());
221 }
222 }
223 }
224
225 tracked_version.ok_or("Cannot build a Pax project without a `pax-*` dependency somewhere in your project's dependency graph. Add e.g. `pax-engine` to your Cargo.toml to resolve this error.")
226}
227
228pub fn copy_dir_recursively(
231 src: &Path,
232 dest: &Path,
233 ignore_list: &[&str],
234) -> Result<(), Box<dyn std::error::Error>> {
235 if src.is_dir() {
236 if ignore_list.contains(&src.file_name().unwrap().to_str().unwrap()) {
238 return Ok(());
239 }
240
241 fs::create_dir_all(dest)?;
244 for entry in fs::read_dir(src)? {
245 let entry = entry?;
246 let path = entry.path();
247 let dest_child = dest.join(path.file_name().ok_or("Invalid file name")?);
248 copy_dir_recursively(&path, &dest_child, ignore_list)?;
249 }
250 } else {
251 fs::copy(src, dest)?;
253 }
254 Ok(())
255}
256
257pub fn get_host_crate_info(cargo_toml_path: &Path) -> HostCrateInfo {
258 let existing_cargo_toml = toml_edit::Document::from_str(
259 &fs::read_to_string(fs::canonicalize(cargo_toml_path).unwrap()).unwrap(),
260 )
261 .expect("Error loading host Cargo.toml");
262
263 let name = existing_cargo_toml["package"]["name"]
264 .as_str()
265 .unwrap()
266 .to_string();
267 let identifier = name.replace("-", "_"); let import_prefix = format!("{}::pax_reexports::", &identifier);
269
270 HostCrateInfo {
271 name,
272 identifier,
273 import_prefix,
274 }
275}