dioxus_crate.rs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. use crate::CliSettings;
  2. use crate::{config::DioxusConfig, TargetArgs};
  3. use crate::{Platform, Result};
  4. use anyhow::Context;
  5. use itertools::Itertools;
  6. use krates::{cm::Target, KrateDetails};
  7. use krates::{cm::TargetKind, Cmd, Krates, NodeId};
  8. use std::path::Path;
  9. use std::path::PathBuf;
  10. use std::sync::Arc;
  11. use toml_edit::Item;
  12. // Contains information about the crate we are currently in and the dioxus config for that crate
  13. #[derive(Clone)]
  14. pub(crate) struct DioxusCrate {
  15. pub(crate) krates: Arc<Krates>,
  16. pub(crate) package: NodeId,
  17. pub(crate) config: DioxusConfig,
  18. pub(crate) target: Target,
  19. pub(crate) settings: CliSettings,
  20. }
  21. pub(crate) static PROFILE_WASM: &str = "wasm-dev";
  22. pub(crate) static PROFILE_ANDROID: &str = "android-dev";
  23. pub(crate) static PROFILE_SERVER: &str = "server-dev";
  24. impl DioxusCrate {
  25. pub(crate) fn new(target: &TargetArgs) -> Result<Self> {
  26. tracing::debug!("Loading crate");
  27. let cmd = Cmd::new();
  28. let builder = krates::Builder::new();
  29. let krates = builder
  30. .build(cmd, |_| {})
  31. .context("Failed to run cargo metadata")?;
  32. let package = find_main_package(&krates, target.package.clone())?;
  33. tracing::debug!("Found package {package:?}");
  34. let dioxus_config = DioxusConfig::load(&krates, package)?.unwrap_or_default();
  35. let package_name = krates[package].name.clone();
  36. let target_kind = if target.example.is_some() {
  37. TargetKind::Example
  38. } else {
  39. TargetKind::Bin
  40. };
  41. let main_package = &krates[package];
  42. let target_name = target
  43. .example
  44. .clone()
  45. .or(target.bin.clone())
  46. .or_else(|| {
  47. if let Some(default_run) = &main_package.default_run {
  48. return Some(default_run.to_string());
  49. }
  50. let bin_count = main_package
  51. .targets
  52. .iter()
  53. .filter(|x| x.kind.contains(&target_kind))
  54. .count();
  55. if bin_count != 1 {
  56. return None;
  57. }
  58. main_package.targets.iter().find_map(|x| {
  59. if x.kind.contains(&target_kind) {
  60. Some(x.name.clone())
  61. } else {
  62. None
  63. }
  64. })
  65. })
  66. .unwrap_or(package_name);
  67. let target = main_package
  68. .targets
  69. .iter()
  70. .find(|target| {
  71. target_name == target.name.as_str() && target.kind.contains(&target_kind)
  72. })
  73. .with_context(|| {
  74. let target_of_kind = |kind|-> String {
  75. let filtered_packages = main_package
  76. .targets
  77. .iter()
  78. .filter_map(|target| {
  79. target.kind.contains(kind).then_some(target.name.as_str())
  80. }).collect::<Vec<_>>();
  81. filtered_packages.join(", ")};
  82. if let Some(example) = &target.example {
  83. let examples = target_of_kind(&TargetKind::Example);
  84. format!("Failed to find example {example}. \nAvailable examples are:\n{}", examples)
  85. } else if let Some(bin) = &target.bin {
  86. let binaries = target_of_kind(&TargetKind::Bin);
  87. format!("Failed to find binary {bin}. \nAvailable binaries are:\n{}", binaries)
  88. } else {
  89. format!("Failed to find target {target_name}. \nIt looks like you are trying to build dioxus in a library crate. \
  90. You either need to run dx from inside a binary crate or build a specific example with the `--example` flag. \
  91. Available examples are:\n{}", target_of_kind(&TargetKind::Example))
  92. }
  93. })?
  94. .clone();
  95. let settings = CliSettings::load();
  96. Ok(Self {
  97. krates: Arc::new(krates),
  98. package,
  99. config: dioxus_config,
  100. target,
  101. settings,
  102. })
  103. }
  104. /// The asset dir we used to support before manganis became the default.
  105. /// This generally was just a folder in your Dioxus.toml called "assets" or "public" where users
  106. /// would store their assets.
  107. ///
  108. /// With manganis you now use `asset!()` and we pick it up automatically.
  109. pub(crate) fn legacy_asset_dir(&self) -> Option<PathBuf> {
  110. self.config
  111. .application
  112. .asset_dir
  113. .clone()
  114. .map(|dir| self.crate_dir().join(dir))
  115. }
  116. /// Get the list of files in the "legacy" asset directory
  117. pub(crate) fn legacy_asset_dir_files(&self) -> Vec<PathBuf> {
  118. let mut files = vec![];
  119. let Some(legacy_asset_dir) = self.legacy_asset_dir() else {
  120. return files;
  121. };
  122. let Ok(read_dir) = legacy_asset_dir.read_dir() else {
  123. return files;
  124. };
  125. for entry in read_dir.flatten() {
  126. files.push(entry.path());
  127. }
  128. files
  129. }
  130. /// Get the outdir specified by the Dioxus.toml, relative to the crate directory.
  131. /// We don't support workspaces yet since that would cause a collision of bundles per project.
  132. pub(crate) fn crate_out_dir(&self) -> Option<PathBuf> {
  133. self.config
  134. .application
  135. .out_dir
  136. .as_ref()
  137. .map(|out_dir| self.crate_dir().join(out_dir))
  138. }
  139. /// Compose an out directory. Represents the typical "dist" directory that
  140. /// is "distributed" after building an application (configurable in the
  141. /// `Dioxus.toml`).
  142. fn internal_out_dir(&self) -> PathBuf {
  143. let dir = self.workspace_dir().join("target").join("dx");
  144. std::fs::create_dir_all(&dir).unwrap();
  145. dir
  146. }
  147. /// Create a workdir for the given platform
  148. /// This can be used as a temporary directory for the build, but in an observable way such that
  149. /// you can see the files in the directory via `target`
  150. ///
  151. /// target/dx/build/app/web/
  152. /// target/dx/build/app/web/public/
  153. /// target/dx/build/app/web/server.exe
  154. pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf {
  155. self.internal_out_dir()
  156. .join(self.executable_name())
  157. .join(if release { "release" } else { "debug" })
  158. .join(platform.build_folder_name())
  159. }
  160. /// target/dx/bundle/app/
  161. /// target/dx/bundle/app/blah.app
  162. /// target/dx/bundle/app/blah.exe
  163. /// target/dx/bundle/app/public/
  164. pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
  165. self.internal_out_dir()
  166. .join(self.executable_name())
  167. .join("bundle")
  168. .join(platform.build_folder_name())
  169. }
  170. /// Get the workspace directory for the crate
  171. pub(crate) fn workspace_dir(&self) -> PathBuf {
  172. self.krates.workspace_root().as_std_path().to_path_buf()
  173. }
  174. /// Get the directory of the crate
  175. pub(crate) fn crate_dir(&self) -> PathBuf {
  176. self.package()
  177. .manifest_path
  178. .parent()
  179. .unwrap()
  180. .as_std_path()
  181. .to_path_buf()
  182. }
  183. /// Get the main source file of the target
  184. pub(crate) fn main_source_file(&self) -> PathBuf {
  185. self.target.src_path.as_std_path().to_path_buf()
  186. }
  187. /// Get the package we are currently in
  188. pub(crate) fn package(&self) -> &krates::cm::Package {
  189. &self.krates[self.package]
  190. }
  191. /// Get the name of the package we are compiling
  192. pub(crate) fn executable_name(&self) -> &str {
  193. &self.target.name
  194. }
  195. /// Get the type of executable we are compiling
  196. pub(crate) fn executable_type(&self) -> krates::cm::TargetKind {
  197. self.target.kind[0].clone()
  198. }
  199. /// Try to autodetect the platform from the package by reading its features
  200. ///
  201. /// Read the default-features list and/or the features list on dioxus to see if we can autodetect the platform
  202. pub(crate) fn autodetect_platform(&self) -> Option<(Platform, String)> {
  203. let krate = self.krates.krates_by_name("dioxus").next()?;
  204. // We're going to accumulate the platforms that are enabled
  205. // This will let us create a better warning if multiple platforms are enabled
  206. let manually_enabled_platforms = self
  207. .krates
  208. .get_enabled_features(krate.kid)?
  209. .iter()
  210. .flat_map(|feature| {
  211. tracing::trace!("Autodetecting platform from feature {feature}");
  212. Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string()))
  213. })
  214. .collect::<Vec<_>>();
  215. if manually_enabled_platforms.len() > 1 {
  216. tracing::error!("Multiple platforms are enabled. Please specify a platform with `--platform <platform>` or set a single default platform using a cargo feature.");
  217. for platform in manually_enabled_platforms {
  218. tracing::error!(" - {platform:?}");
  219. }
  220. return None;
  221. }
  222. if manually_enabled_platforms.len() == 1 {
  223. return manually_enabled_platforms.first().cloned();
  224. }
  225. // Let's try and find the list of platforms from the feature list
  226. // This lets apps that specify web + server to work without specifying the platform.
  227. // This is because we treat `server` as a binary thing rather than a dedicated platform, so at least we can disambiguate it
  228. let possible_platforms = self
  229. .package()
  230. .features
  231. .iter()
  232. .filter_map(|(feature, _features)| {
  233. match Platform::autodetect_from_cargo_feature(feature) {
  234. Some(platform) => Some((platform, feature.to_string())),
  235. None => {
  236. let auto_implicit = _features
  237. .iter()
  238. .filter_map(|f| {
  239. if !f.starts_with("dioxus?/") && !f.starts_with("dioxus/") {
  240. return None;
  241. }
  242. let rest = f
  243. .trim_start_matches("dioxus/")
  244. .trim_start_matches("dioxus?/");
  245. Platform::autodetect_from_cargo_feature(rest)
  246. })
  247. .collect::<Vec<_>>();
  248. if auto_implicit.len() == 1 {
  249. Some((auto_implicit.first().copied().unwrap(), feature.to_string()))
  250. } else {
  251. None
  252. }
  253. }
  254. }
  255. })
  256. .filter(|platform| platform.0 != Platform::Server)
  257. .collect::<Vec<_>>();
  258. if possible_platforms.len() == 1 {
  259. return possible_platforms.first().cloned();
  260. }
  261. None
  262. }
  263. /// Check if dioxus is being built with a particular feature
  264. pub(crate) fn has_dioxus_feature(&self, filter: &str) -> bool {
  265. self.krates.krates_by_name("dioxus").any(|dioxus| {
  266. self.krates
  267. .get_enabled_features(dioxus.kid)
  268. .map(|features| features.contains(filter))
  269. .unwrap_or_default()
  270. })
  271. }
  272. /// Get the features required to build for the given platform
  273. pub(crate) fn feature_for_platform(&self, platform: Platform) -> String {
  274. let package = self.package();
  275. // Try to find the feature that activates the dioxus feature for the given platform
  276. let dioxus_feature = platform.feature_name();
  277. let res = package.features.iter().find_map(|(key, features)| {
  278. // if the feature is just the name of the platform, we use that
  279. if key == dioxus_feature {
  280. return Some(key.clone());
  281. }
  282. // Otherwise look for the feature that starts with dioxus/ or dioxus?/ and matches the platform
  283. for feature in features {
  284. if let Some((_, after_dioxus)) = feature.split_once("dioxus") {
  285. if let Some(dioxus_feature_enabled) =
  286. after_dioxus.trim_start_matches('?').strip_prefix('/')
  287. {
  288. // If that enables the feature we are looking for, return that feature
  289. if dioxus_feature_enabled == dioxus_feature {
  290. return Some(key.clone());
  291. }
  292. }
  293. }
  294. }
  295. None
  296. });
  297. res.unwrap_or_else(|| {
  298. let fallback = format!("dioxus/{}", platform.feature_name()) ;
  299. tracing::debug!(
  300. "Could not find explicit feature for platform {platform}, passing `fallback` instead"
  301. );
  302. fallback
  303. })
  304. }
  305. /// Check if assets should be pre_compressed. This will only be true in release mode if the user
  306. /// has enabled pre_compress in the web config.
  307. pub(crate) fn should_pre_compress_web_assets(&self, release: bool) -> bool {
  308. self.config.web.pre_compress && release
  309. }
  310. // The `opt-level=1` increases build times, but can noticeably decrease time
  311. // between saving changes and being able to interact with an app (for wasm/web). The "overall"
  312. // time difference (between having and not having the optimization) can be
  313. // almost imperceptible (~1 s) but also can be very noticeable (~6 s) — depends
  314. // on setup (hardware, OS, browser, idle load).
  315. //
  316. // Find or create the client and server profiles in the top-level Cargo.toml file
  317. // todo(jon): we should/could make these optional by placing some defaults somewhere
  318. pub(crate) fn initialize_profiles(&self) -> crate::Result<()> {
  319. let config_path = self.workspace_dir().join("Cargo.toml");
  320. let mut config = match std::fs::read_to_string(&config_path) {
  321. Ok(config) => config.parse::<toml_edit::DocumentMut>().map_err(|e| {
  322. crate::Error::Other(anyhow::anyhow!("Failed to parse Cargo.toml: {}", e))
  323. })?,
  324. Err(_) => Default::default(),
  325. };
  326. if let Item::Table(table) = config
  327. .as_table_mut()
  328. .entry("profile")
  329. .or_insert(Item::Table(Default::default()))
  330. {
  331. if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_WASM) {
  332. let mut client = toml_edit::Table::new();
  333. client.insert("inherits", Item::Value("dev".into()));
  334. client.insert("opt-level", Item::Value(1.into()));
  335. entry.insert(Item::Table(client));
  336. }
  337. if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_SERVER) {
  338. let mut server = toml_edit::Table::new();
  339. server.insert("inherits", Item::Value("dev".into()));
  340. entry.insert(Item::Table(server));
  341. }
  342. if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_ANDROID) {
  343. let mut android = toml_edit::Table::new();
  344. android.insert("inherits", Item::Value("dev".into()));
  345. entry.insert(Item::Table(android));
  346. }
  347. }
  348. std::fs::write(config_path, config.to_string())
  349. .context("Failed to write profiles to Cargo.toml")?;
  350. Ok(())
  351. }
  352. fn default_ignore_list(&self) -> Vec<&'static str> {
  353. vec![
  354. ".git",
  355. ".github",
  356. ".vscode",
  357. "target",
  358. "node_modules",
  359. "dist",
  360. "*~",
  361. ".*",
  362. "*.lock",
  363. "*.log",
  364. ]
  365. }
  366. /// Create a new gitignore map for this target crate
  367. ///
  368. /// todo(jon): this is a bit expensive to build, so maybe we should cache it?
  369. pub fn workspace_gitignore(&self) -> ignore::gitignore::Gitignore {
  370. let crate_dir = self.crate_dir();
  371. let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(&crate_dir);
  372. ignore_builder.add(crate_dir.join(".gitignore"));
  373. let workspace_dir = self.workspace_dir();
  374. ignore_builder.add(workspace_dir.join(".gitignore"));
  375. for path in self.default_ignore_list() {
  376. ignore_builder
  377. .add_line(None, path)
  378. .expect("failed to add path to file excluded");
  379. }
  380. ignore_builder.build().unwrap()
  381. }
  382. /// Return the version of the wasm-bindgen crate if it exists
  383. pub fn wasm_bindgen_version(&self) -> Option<String> {
  384. self.krates
  385. .krates_by_name("wasm-bindgen")
  386. .next()
  387. .map(|krate| krate.krate.version.to_string())
  388. }
  389. pub(crate) fn default_platform(&self) -> Option<Platform> {
  390. let default = self.package().features.get("default")?;
  391. // we only trace features 1 level deep..
  392. for feature in default.iter() {
  393. // If the user directly specified a platform we can just use that.
  394. if feature.starts_with("dioxus/") {
  395. let dx_feature = feature.trim_start_matches("dioxus/");
  396. let auto = Platform::autodetect_from_cargo_feature(dx_feature);
  397. if auto.is_some() {
  398. return auto;
  399. }
  400. }
  401. // If the user is specifying an internal feature that points to a platform, we can use that
  402. let internal_feature = self.package().features.get(feature);
  403. if let Some(internal_feature) = internal_feature {
  404. for feature in internal_feature {
  405. if feature.starts_with("dioxus/") {
  406. let dx_feature = feature.trim_start_matches("dioxus/");
  407. let auto = Platform::autodetect_from_cargo_feature(dx_feature);
  408. if auto.is_some() {
  409. return auto;
  410. }
  411. }
  412. }
  413. }
  414. }
  415. None
  416. }
  417. /// Gather the features that are enabled for the package
  418. pub(crate) fn platformless_features(&self) -> Vec<String> {
  419. let default = self.package().features.get("default").unwrap();
  420. let mut kept_features = vec![];
  421. // Only keep the top-level features in the default list that don't point to a platform directly
  422. // IE we want to drop `web` if default = ["web"]
  423. 'top: for feature in default {
  424. // Don't keep features that point to a platform via dioxus/blah
  425. if feature.starts_with("dioxus/") {
  426. let dx_feature = feature.trim_start_matches("dioxus/");
  427. if Platform::autodetect_from_cargo_feature(dx_feature).is_some() {
  428. continue 'top;
  429. }
  430. }
  431. // Don't keep features that point to a platform via an internal feature
  432. if let Some(internal_feature) = self.package().features.get(feature) {
  433. for feature in internal_feature {
  434. if feature.starts_with("dioxus/") {
  435. let dx_feature = feature.trim_start_matches("dioxus/");
  436. if Platform::autodetect_from_cargo_feature(dx_feature).is_some() {
  437. continue 'top;
  438. }
  439. }
  440. }
  441. }
  442. // Otherwise we can keep it
  443. kept_features.push(feature.to_string());
  444. }
  445. kept_features
  446. }
  447. /// Return the list of paths that we should watch for changes.
  448. pub(crate) fn watch_paths(&self) -> Vec<PathBuf> {
  449. let mut watched_paths = vec![];
  450. // Get a list of *all* the crates with Rust code that we need to watch.
  451. // This will end up being dependencies in the workspace and non-workspace dependencies on the user's computer.
  452. let mut watched_crates = self.local_dependencies();
  453. watched_crates.push(self.crate_dir());
  454. // Now, watch all the folders in the crates, but respecting their respective ignore files
  455. for krate_root in watched_crates {
  456. // Build the ignore builder for this crate, but with our default ignore list as well
  457. let ignore = self.ignore_for_krate(&krate_root);
  458. for entry in krate_root.read_dir().unwrap() {
  459. let Ok(entry) = entry else {
  460. continue;
  461. };
  462. if ignore
  463. .matched(entry.path(), entry.path().is_dir())
  464. .is_ignore()
  465. {
  466. continue;
  467. }
  468. watched_paths.push(entry.path().to_path_buf());
  469. }
  470. }
  471. watched_paths.dedup();
  472. watched_paths
  473. }
  474. fn ignore_for_krate(&self, path: &Path) -> ignore::gitignore::Gitignore {
  475. let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(path);
  476. for path in self.default_ignore_list() {
  477. ignore_builder
  478. .add_line(None, path)
  479. .expect("failed to add path to file excluded");
  480. }
  481. ignore_builder.build().unwrap()
  482. }
  483. /// Get all the Manifest paths for dependencies that we should watch. Will not return anything
  484. /// in the `.cargo` folder - only local dependencies will be watched.
  485. ///
  486. /// This returns a list of manifest paths
  487. ///
  488. /// Extend the watch path to include:
  489. ///
  490. /// - the assets directory - this is so we can hotreload CSS and other assets by default
  491. /// - the Cargo.toml file - this is so we can hotreload the project if the user changes dependencies
  492. /// - the Dioxus.toml file - this is so we can hotreload the project if the user changes the Dioxus config
  493. pub(crate) fn local_dependencies(&self) -> Vec<PathBuf> {
  494. let mut paths = vec![];
  495. for (dependency, _edge) in self.krates.get_deps(self.package) {
  496. let krate = match dependency {
  497. krates::Node::Krate { krate, .. } => krate,
  498. krates::Node::Feature { krate_index, .. } => &self.krates[krate_index.index()],
  499. };
  500. if krate
  501. .manifest_path
  502. .components()
  503. .any(|c| c.as_str() == ".cargo")
  504. {
  505. continue;
  506. }
  507. paths.push(
  508. krate
  509. .manifest_path
  510. .parent()
  511. .unwrap()
  512. .to_path_buf()
  513. .into_std_path_buf(),
  514. );
  515. }
  516. paths
  517. }
  518. pub(crate) fn all_watched_crates(&self) -> Vec<PathBuf> {
  519. let mut krates: Vec<PathBuf> = self
  520. .local_dependencies()
  521. .into_iter()
  522. .map(|p| {
  523. p.parent()
  524. .expect("Local manifest to exist and have a parent")
  525. .to_path_buf()
  526. })
  527. .chain(Some(self.crate_dir()))
  528. .collect();
  529. krates.dedup();
  530. krates
  531. }
  532. pub(crate) fn android_ndk(&self) -> Option<PathBuf> {
  533. // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang"
  534. static PATH: once_cell::sync::Lazy<Option<PathBuf>> = once_cell::sync::Lazy::new(|| {
  535. use std::env::var;
  536. use tracing::debug;
  537. fn var_or_debug(name: &str) -> Option<PathBuf> {
  538. var(name)
  539. .inspect_err(|_| debug!("{name} not set"))
  540. .ok()
  541. .map(PathBuf::from)
  542. }
  543. // attempt to autodetect the ndk path from env vars (usually set by the shell)
  544. let auto_detected_ndk =
  545. var_or_debug("NDK_HOME").or_else(|| var_or_debug("ANDROID_NDK_HOME"));
  546. if let Some(home) = auto_detected_ndk {
  547. return Some(home);
  548. }
  549. let sdk = var_or_debug("ANDROID_SDK_ROOT")
  550. .or_else(|| var_or_debug("ANDROID_SDK"))
  551. .or_else(|| var_or_debug("ANDROID_HOME"))?;
  552. let ndk = sdk.join("ndk");
  553. ndk.read_dir()
  554. .ok()?
  555. .flatten()
  556. .map(|dir| (dir.file_name(), dir.path()))
  557. .sorted()
  558. .last()
  559. .map(|(_, path)| path.to_path_buf())
  560. });
  561. PATH.clone()
  562. }
  563. pub(crate) fn mobile_org(&self) -> String {
  564. let identifier = self.bundle_identifier();
  565. let mut split = identifier.splitn(3, '.');
  566. let sub = split
  567. .next()
  568. .expect("Identifier to have at least 3 periods like `com.example.app`");
  569. let tld = split
  570. .next()
  571. .expect("Identifier to have at least 3 periods like `com.example.app`");
  572. format!("{}.{}", sub, tld)
  573. }
  574. pub(crate) fn bundled_app_name(&self) -> String {
  575. use convert_case::{Case, Casing};
  576. self.executable_name().to_case(Case::Pascal)
  577. }
  578. pub(crate) fn full_mobile_app_name(&self) -> String {
  579. format!("{}.{}", self.mobile_org(), self.bundled_app_name())
  580. }
  581. pub(crate) fn bundle_identifier(&self) -> String {
  582. if let Some(identifier) = self.config.bundle.identifier.clone() {
  583. return identifier.clone();
  584. }
  585. format!("com.example.{}", self.bundled_app_name())
  586. }
  587. }
  588. impl std::fmt::Debug for DioxusCrate {
  589. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  590. f.debug_struct("DioxusCrate")
  591. .field("package", &self.krates[self.package])
  592. .field("dioxus_config", &self.config)
  593. .field("target", &self.target)
  594. .finish()
  595. }
  596. }
  597. // Find the main package in the workspace
  598. fn find_main_package(krates: &Krates, package: Option<String>) -> Result<NodeId> {
  599. if let Some(package) = package {
  600. let mut workspace_members = krates.workspace_members();
  601. let found = workspace_members.find_map(|node| {
  602. if let krates::Node::Krate { id, krate, .. } = node {
  603. if krate.name == package {
  604. return Some(id);
  605. }
  606. }
  607. None
  608. });
  609. if found.is_none() {
  610. tracing::error!("Could not find package {package} in the workspace. Did you forget to add it to the workspace?");
  611. tracing::error!("Packages in the workspace:");
  612. for package in krates.workspace_members() {
  613. if let krates::Node::Krate { krate, .. } = package {
  614. tracing::error!("{}", krate.name());
  615. }
  616. }
  617. }
  618. let kid = found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?;
  619. return Ok(krates.nid_for_kid(kid).unwrap());
  620. };
  621. // Otherwise find the package that is the closest parent of the current directory
  622. let current_dir = std::env::current_dir()?;
  623. let current_dir = current_dir.as_path();
  624. // Go through each member and find the path that is a parent of the current directory
  625. let mut closest_parent = None;
  626. for member in krates.workspace_members() {
  627. if let krates::Node::Krate { id, krate, .. } = member {
  628. let member_path = krate.manifest_path.parent().unwrap();
  629. if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {
  630. let len = path.components().count();
  631. match closest_parent {
  632. Some((_, closest_parent_len)) => {
  633. if len < closest_parent_len {
  634. closest_parent = Some((id, len));
  635. }
  636. }
  637. None => {
  638. closest_parent = Some((id, len));
  639. }
  640. }
  641. }
  642. }
  643. }
  644. let kid = closest_parent
  645. .map(|(id, _)| id)
  646. .with_context(|| {
  647. let bin_targets = krates.workspace_members().filter_map(|krate|match krate {
  648. krates::Node::Krate { krate, .. } if krate.targets.iter().any(|t| t.kind.contains(&krates::cm::TargetKind::Bin))=> {
  649. Some(format!("- {}", krate.name))
  650. }
  651. _ => None
  652. }).collect::<Vec<_>>();
  653. format!("Failed to find binary package to build.\nYou need to either run dx from inside a binary crate or specify a binary package to build with the `--package` flag. Try building again with one of the binary packages in the workspace:\n{}", bin_targets.join("\n"))
  654. })?;
  655. let package = krates.nid_for_kid(kid).unwrap();
  656. Ok(package)
  657. }