* initial version * make custom permission work without const-vec change * wiring up with cli * yey symbol found * wip * holy moly * proper ui * cleanup * okay * wip cleanup * reduce boilerplate code into mobile-core * Delete copilot-instructions.md * revert hello-world example for low diff * fix android build in mobile-core * rename permission to static_permission * small success with popup location on android * android popup working * Refactor permission checks for readability Improves code formatting in has_location_permission and check_permission functions by splitting long lines and updating function signatures for better readability. No functional changes were made. * wip consolidate all java files and build from cli instead of build.rs * few cli cleanups * wip java_plugin macro * cleanup cleanup cleanup * Update java_plugin macro to use full relative file paths The java_plugin macro now accepts full relative paths for Java files instead of assuming a fixed directory structure. Documentation and usage examples have been updated to reflect this change, improving flexibility and clarity for specifying file locations. * Improve iOS geolocation permission and retrieval logic Added checks for authorization status before requesting location permissions and retrieving location data. Now only requests permission if not determined, and attempts to retrieve cached location before starting location updates, improving efficiency and user experience. * Add ios_plugin macro for iOS framework metadata Introduces the ios_plugin! macro in mobile-core-macro for declarative iOS framework metadata embedding. Updates mobile-core to re-export the macro and refactors mobile-geolocation to use ios_plugin! instead of manual linker attributes. * not needed * simplify the plugin * rename mobile-core to platform-bridge and mobile-core-macro to platform-bridge-macro * Refactor metadata serialization buffer initialization Replaces inline creation of ConstVec with a named EMPTY constant for buffer initialization in metadata serialization. This improves code clarity and maintainability. * mobile-geolocation out of dioxus * take the example also out of dioxus * remove example from cargo toml * remove demo css * revert hello-world example * revert mac.plist * revert gitignore * Remove basic usage example for permissions crate Deleted the basic_usage.rs example file from the permissions crate. This change may be part of a cleanup or refactoring to remove outdated or unnecessary example code. * Delete plan.md * remove dups * cleanup java build * cleanup permission symbol extraction * update todo * small slop of tested permissions in enums for minimum diff * Refactor symbol extraction into linker_symbols module Moved generic symbol extraction logic from android_java.rs and permissions.rs into a new linker_symbols.rs module. Updated both files to use the shared utilities for finding symbol offsets, reducing code duplication and improving maintainability. * wip consolidate ios and macos as darwin * Add docs and re-export for MainThreadCell and MainThreadMarker Expanded documentation for MainThreadCell, clarifying usage and safety. Changed visibility of MainThreadCell to public, improved method docs, and re-exported MainThreadMarker in darwin/mod.rs for easier access. * Update Darwin framework symbol and section names Changed the framework symbol prefix from '__DARWIN_FRAMEWORK__' to '__DARWIN_FW__' and updated the Mach-O section name from '__darwin_framework' to '__darwin_fw' for compliance with section name length limits and consistency. * Fix Info.plist path handling for iOS and macOS Adds comments and corrects the path to Info.plist for iOS and macOS app bundles. For iOS, Info.plist is at the root of the .app bundle, while for macOS it is inside Contents/. Also improves warning message to include the missing path. * Handle missing permission symbols and web bundle case Improves permission extraction by returning an empty manifest when no permission symbols are found or when building a web bundle, as permissions are runtime-only in that case. Adds debug logging for missing symbols and ensures robust handling of these scenarios. * objc2 over framework * windows, linux, web all permissions are runtime so cleanup * only include permissions for build time * cleanup unnecessary test * more cleanup * extract shared into dx-macro-helpers * rc.3 to 0.7.0 for const-serialize * Add CustomPermissionBuilder and enhance PermissionBuilder for better permission management - Introduced `CustomPermissionBuilder` for creating custom permissions with platform-specific identifiers. - Enhanced `PermissionBuilder` to support custom permissions and location permissions with a clear builder pattern. - Updated documentation and examples in the macro README to reflect new usage patterns for custom and location permissions. - Improved parsing logic in the macro to handle both builder and direct construction patterns for permissions. * clippy fixes * implement const cbor encoding + decoding for basic types * Add #[used] attribute to linker sections for optimization protection - Added #[used] attribute to linker sections in both permission and Android plugin macros to prevent the linker from optimizing away symbols, ensuring they are preserved even if unused. - Created static references to linker sections to enhance defense-in-depth against optimization issues. * implement map encoding and decoding * fix map test * wip unified linker symbol * cleanup permissions * pipe java sources with manganis as well * all tests passing * dynamically sized arrays * smaller str encoding * swap array and list and add more documentation * move serialization and deserialization for each struct together * support both legacy and new assets * bump const serialize version * fix asset conversion code * pull out manganis symbol struct from tuble * test reading old asset versions * more list/array cleanup and bump rust version * fix clippy * Revert "pipe java sources with manganis as well" This reverts commit ddea9a459d9ac26f3f037a74d9ae0de3edfced67. * Revert "cleanup permissions" This reverts commit 7b18500155237403e179f0783cb8903a4f158b80. * Revert "wip unified linker symbol" This reverts commit 813e33feddb30d26868f6ac6e6da318402ac138f. * Revert "Add #[used] attribute to linker sections for optimization protection" This reverts commit a94a7a3a5e8eee80060de9d66a53aabfcf554f96. * update cargo lock * wip permission through assets * abit cleanup * fix reexports * fix assets * Fix the permissions piping * some small cleanups * checkout const-serialize as per #4932 and fix build * invoking swift as a package * geolocation package & example dumping for easy testing * keep geolocation simple for testing * cleanups * pipe everything through __ASSETS__ and cleanup * remove linker_symbols helper file * example * re example * copy build methos into mobile-plugin-build crate * Add conditional import for dioxus_07 feature Introduces a conditional import to use the dioxus_07 crate when the corresponding feature is enabled, improving compatibility with different dioxus versions. * Improve error handling and JNI reference usage Adds a null check for Objective-C instance initialization in the iOS geolocation plugin to prevent errors on failed allocation. Refactors Android callback to use local JNI references instead of creating unnecessary global references, reducing potential memory leaks. * revert changes on playwright * Update dioxus dependency to 0.7.2 in Cargo.lock Removed dioxus 0.7.1 from dependencies, retaining only dioxus 0.7.2 for the dioxus-cli-optimization-test package. * small cleanup * Add legacy const-serialize 0.7 support to dx-macro-helpers Introduces support for the const-serialize 0.7 format in dx-macro-helpers by adding a new dependency and export, as well as a helper function for serialization with layout padding. Updates manganis to use the new helper for serializing assets in the legacy format, simplifying code and improving maintainability. * Add helper for legacy and current linker sections Introduces generate_link_sections_with_legacy in dx-macro-helpers to emit both legacy and current linker sections for compatibility. Refactors manganis-macro to use this new helper, reducing duplication and improving maintainability. * Fix lints * Fix clippy and doctest * Refactor asset bundling and allow large enum variant Refactored Asset::bundled to use Vec<u8> instead of ConstVec for reading volatile slices, simplifying the code and improving compatibility. Added #[allow(clippy::large_enum_variant)] to SymbolData enum to suppress Clippy warnings about large enum variants. * Update license URLs in README to use angle brackets Changed license URLs in the README from parentheses to angle brackets for improved formatting and clarity. * Fix asset deserialization with correct buffer type Replaces the use of `bytes.read()` with `ConstReadBuffer::new(bytes.as_ref())` for asset deserialization, ensuring compatibility with the expected deserialization API. * Refactor geolocation and asset handling for clarity Removed unnecessary clones in geolocation example event handlers to simplify state management. Updated SymbolDataOrAsset to store SymbolData in a Box for improved memory handling and adjusted related logic. Fixed documentation example in geolocation lib to include PermissionState in the import. * Update geolocation example and permissions usage docs Removes unused variables in the geolocation example's App function. Updates the permissions crate documentation to reflect new API usage with PermissionBuilder and LocationPrecision. * Update permissions-macro to use permissions crate Switched imports and dev-dependency from permissions_core to permissions in permissions-macro. Updated documentation and examples to reflect the new crate usage. * rollback const-serialize changes * move items around (doesn't compile) * convert gelocation plugin to example * remove dx-macro-helpers * we're just gonna full-send linker everything * rollback desktop changes * remove gradle * fix most compile? * fix dioxus vers * small cleanups * add claude design docs * add ffi macro * remove * loads but not linking yet * fix output leak * a little bit closer but not quite * improve android code gen * sheeeeee works!!! * small tweaks to demo * replace ios/android plugin with ffi macro entirely * simplify ffi module * simplify again * condense / rip out some stuff from manganis * remove types form public api * wip, permissions * oops I broke it * I unbroke it * move arch doc * remove extra manifest types * drop bindgen file * remove permission * small cleanup * remove unsafecall mtm thing * yoink perms * wire up manifest * Add deeplinks * add schema generation * update schema * wip.... * fix recurse limit schema generation * wip, muck around with schema * add inline dioxus.toml support * merge bundle config schemas * claude my man * Remove cdecl, annotate ffi * rename design docs * wip: live activities * wip: apple plugin bundling * wip * get live activities linking properly * small cleanups * remove sidecars * remove widget * yoink legacy * cleanup * more fixups * wire up more things to dioxus.toml * fix workspace join * final cleanups * fmt * final bit of clippy * ci tweak * update schema * fmt, doc * update schema docs --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
650 lines
24 KiB
Rust
650 lines
24 KiB
Rust
use crate::styles::GLOW_STYLE;
|
|
use crate::CliSettings;
|
|
use crate::Result;
|
|
use crate::{config::DioxusConfig, AndroidTools};
|
|
use anyhow::{bail, Context};
|
|
use ignore::gitignore::Gitignore;
|
|
use krates::{semver::Version, KrateDetails, LockOptions};
|
|
use krates::{Cmd, Krates, NodeId};
|
|
use std::sync::Arc;
|
|
use std::{collections::HashSet, path::Path};
|
|
use std::{path::PathBuf, time::Duration};
|
|
use target_lexicon::Triple;
|
|
use tokio::process::Command;
|
|
|
|
pub struct Workspace {
|
|
pub(crate) krates: Krates,
|
|
pub(crate) settings: CliSettings,
|
|
pub(crate) wasm_opt: Option<PathBuf>,
|
|
pub(crate) sysroot: PathBuf,
|
|
pub(crate) rustc_version: String,
|
|
pub(crate) ignore: Gitignore,
|
|
pub(crate) cargo_toml: cargo_toml::Manifest,
|
|
pub(crate) android_tools: Option<Arc<AndroidTools>>,
|
|
}
|
|
|
|
impl Workspace {
|
|
/// Load the workspace from the current directory. This is cached and will only be loaded once.
|
|
pub async fn current() -> Result<Arc<Workspace>> {
|
|
static WS: tokio::sync::Mutex<Option<Arc<Workspace>>> = tokio::sync::Mutex::const_new(None);
|
|
|
|
// Lock the workspace to prevent multiple threads from loading it at the same time
|
|
// If loading the workspace failed the first time, it won't be set and therefore permeate an error.
|
|
let mut lock = WS.lock().await;
|
|
if let Some(ws) = lock.as_ref() {
|
|
return Ok(ws.clone());
|
|
}
|
|
|
|
let krates_future = tokio::task::spawn_blocking(|| {
|
|
let manifest_options = crate::logging::VERBOSITY.get().unwrap();
|
|
let lock_options = LockOptions {
|
|
frozen: manifest_options.frozen,
|
|
locked: manifest_options.locked,
|
|
offline: manifest_options.offline,
|
|
};
|
|
|
|
let mut cmd = Cmd::new();
|
|
cmd.lock_opts(lock_options);
|
|
|
|
let mut builder = krates::Builder::new();
|
|
builder.workspace(true);
|
|
let res = builder.build(cmd, |_| {})?;
|
|
|
|
if !lock_options.offline {
|
|
if let Ok(res) = std::env::var("SIMULATE_SLOW_NETWORK") {
|
|
std::thread::sleep(Duration::from_secs(res.parse().unwrap_or(5)));
|
|
}
|
|
}
|
|
|
|
Ok(res) as Result<Krates, krates::Error>
|
|
});
|
|
|
|
let spin_future = async move {
|
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
|
eprintln!("{GLOW_STYLE}warning{GLOW_STYLE:#}: Waiting for cargo-metadata...");
|
|
tokio::time::sleep(Duration::from_millis(2000)).await;
|
|
for x in 1..=100 {
|
|
tokio::time::sleep(Duration::from_millis(2000)).await;
|
|
eprintln!("{GLOW_STYLE}warning{GLOW_STYLE:#}: (Try {x}) Taking a while...");
|
|
|
|
if x % 10 == 0 {
|
|
eprintln!("{GLOW_STYLE}warning{GLOW_STYLE:#}: maybe check your network connection or build lock?");
|
|
}
|
|
}
|
|
};
|
|
|
|
let krates = tokio::select! {
|
|
f = krates_future => {
|
|
let res = f?;
|
|
if let Err(krates::Error::Metadata(e)) = res {
|
|
bail!("{e}");
|
|
}
|
|
res?
|
|
},
|
|
_ = spin_future => bail!("cargo metadata took too long to respond, try again with --offline"),
|
|
};
|
|
|
|
let settings = CliSettings::global_or_default();
|
|
let sysroot = Self::get_rustc_sysroot()
|
|
.await
|
|
.context("Failed to get rustc sysroot")?;
|
|
let rustc_version = Self::get_rustc_version()
|
|
.await
|
|
.context("Failed to get rustc version")?;
|
|
|
|
let wasm_opt = which::which("wasm-opt").ok();
|
|
|
|
let ignore = Self::workspace_gitignore(krates.workspace_root().as_std_path());
|
|
|
|
let cargo_toml = crate::cargo_toml::load_manifest_from_path(
|
|
krates.workspace_root().join("Cargo.toml").as_std_path(),
|
|
)
|
|
.context("Failed to load Cargo.toml")?;
|
|
|
|
let android_tools = crate::build::get_android_tools();
|
|
|
|
let workspace = Arc::new(Self {
|
|
krates,
|
|
settings,
|
|
wasm_opt,
|
|
sysroot: sysroot.trim().into(),
|
|
rustc_version: rustc_version.trim().into(),
|
|
ignore,
|
|
cargo_toml,
|
|
android_tools,
|
|
});
|
|
|
|
tracing::debug!(
|
|
r#"Initialized workspace:
|
|
• sysroot: {sysroot}
|
|
• rustc version: {rustc_version}
|
|
• workspace root: {workspace_root}
|
|
• dioxus versions: [{dioxus_versions:?}]"#,
|
|
sysroot = workspace.sysroot.display(),
|
|
rustc_version = workspace.rustc_version,
|
|
workspace_root = workspace.workspace_root().display(),
|
|
dioxus_versions = workspace
|
|
.dioxus_versions()
|
|
.iter()
|
|
.map(|v| v.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
);
|
|
|
|
lock.replace(workspace.clone());
|
|
|
|
Ok(workspace)
|
|
}
|
|
|
|
pub fn android_tools(&self) -> Result<Arc<AndroidTools>> {
|
|
self
|
|
.android_tools
|
|
.clone()
|
|
.context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")
|
|
}
|
|
|
|
pub fn is_release_profile(&self, profile: &str) -> bool {
|
|
// If the profile is "release" or ends with "-release" like the default platform release profiles,
|
|
// always put it in the release category.
|
|
if profile == "release" || profile.ends_with("-release") {
|
|
return true;
|
|
}
|
|
|
|
// Check if the profile inherits from release by traversing the `inherits` chain
|
|
let mut current_profile_name = profile;
|
|
|
|
// Try to find the current profile in the custom profiles section
|
|
while let Some(profile_settings) = self.cargo_toml.profile.custom.get(current_profile_name)
|
|
{
|
|
// Check what this profile inherits from
|
|
match &profile_settings.inherits {
|
|
// Otherwise, continue checking the profile it inherits from
|
|
Some(inherits_name) => current_profile_name = inherits_name,
|
|
|
|
// This profile doesn't explicitly inherit anything, so the chain ends here.
|
|
// Since it didn't lead to "release", return false.
|
|
None => break,
|
|
}
|
|
|
|
if current_profile_name == "release" {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
pub fn check_dioxus_version_against_cli(&self) {
|
|
let dx_semver = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
|
let dioxus_versions = self.dioxus_versions();
|
|
|
|
tracing::trace!("dx version: {}", dx_semver);
|
|
tracing::trace!("dioxus versions: {:?}", dioxus_versions);
|
|
|
|
// if there are no dioxus versions in the workspace, we don't need to check anything
|
|
// dx is meant to be compatible with non-dioxus projects too.
|
|
if dioxus_versions.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let min = dioxus_versions.iter().min().unwrap();
|
|
let max = dioxus_versions.iter().max().unwrap();
|
|
|
|
// If the minimum dioxus version is greater than the current cli version, warn the user
|
|
if min > &dx_semver
|
|
|| max < &dx_semver
|
|
|| dioxus_versions.iter().any(|f| f.pre != dx_semver.pre)
|
|
{
|
|
tracing::error!(
|
|
r#"🚫dx and dioxus versions are incompatible!
|
|
• dx version: {dx_semver}
|
|
• dioxus versions: [{}]"#,
|
|
dioxus_versions
|
|
.iter()
|
|
.map(|v| v.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Get all the versions of dioxus in the workspace
|
|
pub fn dioxus_versions(&self) -> Vec<Version> {
|
|
let mut versions = HashSet::new();
|
|
for krate in self.krates.krates() {
|
|
if krate.name == "dioxus" {
|
|
versions.insert(krate.version.clone());
|
|
}
|
|
}
|
|
let mut versions = versions.into_iter().collect::<Vec<_>>();
|
|
versions.sort();
|
|
versions
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn rust_lld(&self) -> PathBuf {
|
|
self.sysroot
|
|
.join("lib")
|
|
.join("rustlib")
|
|
.join(Triple::host().to_string())
|
|
.join("bin")
|
|
.join("rust-lld")
|
|
}
|
|
|
|
/// Return the path to the `cc` compiler
|
|
///
|
|
/// This is used for the patching system to run the linker.
|
|
/// We could also just use lld given to us by rust itself.
|
|
pub fn cc(&self) -> PathBuf {
|
|
PathBuf::from("cc")
|
|
}
|
|
|
|
/// The windows linker
|
|
pub fn lld_link(&self) -> PathBuf {
|
|
self.gcc_ld_dir().join("lld-link")
|
|
}
|
|
|
|
pub fn wasm_ld(&self) -> PathBuf {
|
|
self.gcc_ld_dir().join("wasm-ld")
|
|
}
|
|
|
|
pub fn select_ranlib() -> Option<PathBuf> {
|
|
// prefer the modern llvm-ranlib if they have it
|
|
which::which("llvm-ranlib")
|
|
.or_else(|_| which::which("ranlib"))
|
|
.ok()
|
|
}
|
|
|
|
/// Return the version of the wasm-bindgen crate if it exists
|
|
pub fn wasm_bindgen_version(&self) -> Option<String> {
|
|
self.krates
|
|
.krates_by_name("wasm-bindgen")
|
|
.next()
|
|
.map(|krate| krate.krate.version.to_string())
|
|
}
|
|
|
|
// wasm-ld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/wasm-ld
|
|
// rust-lld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rust-lld
|
|
fn gcc_ld_dir(&self) -> PathBuf {
|
|
self.sysroot
|
|
.join("lib")
|
|
.join("rustlib")
|
|
.join(Triple::host().to_string())
|
|
.join("bin")
|
|
.join("gcc-ld")
|
|
}
|
|
|
|
// wasm-ld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/wasm-ld
|
|
// rust-lld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rust-lld
|
|
pub fn rustc_objcopy(&self) -> PathBuf {
|
|
self.sysroot
|
|
.join("lib")
|
|
.join("rustlib")
|
|
.join(Triple::host().to_string())
|
|
.join("bin")
|
|
.join("rust-objcopy")
|
|
}
|
|
|
|
// ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib
|
|
pub fn rustc_objcopy_dylib_path(&self) -> PathBuf {
|
|
self.sysroot.join("lib")
|
|
}
|
|
|
|
/// Find the "main" package in the workspace. There might not be one!
|
|
pub fn find_main_package(&self, package: Option<String>) -> Result<NodeId> {
|
|
if let Some(package) = package {
|
|
let mut workspace_members = self.krates.workspace_members();
|
|
let found = workspace_members.find_map(|node| {
|
|
if let krates::Node::Krate { id, krate, .. } = node {
|
|
if krate.name == package {
|
|
return Some(id);
|
|
}
|
|
}
|
|
None
|
|
});
|
|
|
|
if found.is_none() {
|
|
tracing::error!("Could not find package {package} in the workspace. Did you forget to add it to the workspace?");
|
|
tracing::error!("Packages in the workspace:");
|
|
for package in self.krates.workspace_members() {
|
|
if let krates::Node::Krate { krate, .. } = package {
|
|
tracing::error!("{}", krate.name());
|
|
}
|
|
}
|
|
}
|
|
|
|
let kid = found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?;
|
|
|
|
return Ok(self.krates.nid_for_kid(kid).unwrap());
|
|
};
|
|
|
|
// if we have default members specified, try them first
|
|
if let Some(ws) = &self.cargo_toml.workspace {
|
|
for default in &ws.default_members {
|
|
let mut workspace_members = self.krates.workspace_members();
|
|
let default_member_path = std::fs::canonicalize(default).unwrap();
|
|
|
|
let found = workspace_members.find_map(|node| {
|
|
if let krates::Node::Krate { id, krate, .. } = node {
|
|
// Skip this default member if it doesn't have any binary targets
|
|
if !krate
|
|
.targets
|
|
.iter()
|
|
.any(|t| t.kind.contains(&krates::cm::TargetKind::Bin))
|
|
{
|
|
return None;
|
|
}
|
|
let member_path =
|
|
std::fs::canonicalize(krate.manifest_path.parent().unwrap()).unwrap();
|
|
if member_path == default_member_path {
|
|
return Some(id);
|
|
}
|
|
}
|
|
None
|
|
});
|
|
|
|
if let Some(kid) = found {
|
|
return Ok(self.krates.nid_for_kid(kid).unwrap());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise find the package that is the closest parent of the current directory
|
|
let current_dir = std::env::current_dir()?;
|
|
let current_dir = current_dir.as_path();
|
|
|
|
// Go through each member and find the path that is a parent of the current directory
|
|
let mut closest_parent = None;
|
|
for member in self.krates.workspace_members() {
|
|
if let krates::Node::Krate { id, krate, .. } = member {
|
|
let member_path = krate.manifest_path.parent().unwrap();
|
|
if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {
|
|
let len = path.components().count();
|
|
match closest_parent {
|
|
Some((_, closest_parent_len)) => {
|
|
if len < closest_parent_len {
|
|
closest_parent = Some((id, len));
|
|
}
|
|
}
|
|
None => {
|
|
closest_parent = Some((id, len));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let kid = closest_parent
|
|
.map(|(id, _)| id)
|
|
.with_context(|| {
|
|
let bin_targets = self.krates.workspace_members().filter_map(|krate|match krate {
|
|
krates::Node::Krate { krate, .. } if krate.targets.iter().any(|t| t.kind.contains(&krates::cm::TargetKind::Bin))=> {
|
|
Some(format!("- {}", krate.name))
|
|
}
|
|
_ => None
|
|
}).collect::<Vec<_>>();
|
|
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"))
|
|
})?;
|
|
|
|
let package = self.krates.nid_for_kid(kid).unwrap();
|
|
Ok(package)
|
|
}
|
|
|
|
/// Load the Dioxus.toml configuration for a package.
|
|
///
|
|
/// Optionally accepts a source file path to extract inline configuration from doc comments.
|
|
/// Inline config is merged with the base Dioxus.toml, with inline values taking precedence.
|
|
///
|
|
/// This allows examples and binaries to embed config in their doc comments:
|
|
/// ```rust,ignore
|
|
/// //! ```dioxus.toml
|
|
/// //! [bundle]
|
|
/// //! identifier = "com.example.app"
|
|
/// //! ```
|
|
/// ```
|
|
pub fn load_dioxus_config(
|
|
&self,
|
|
package: NodeId,
|
|
source_file: Option<&Path>,
|
|
) -> Result<Option<DioxusConfig>> {
|
|
// Walk up from the cargo.toml to the root of the workspace looking for Dioxus.toml
|
|
let mut current_dir = self.krates[package]
|
|
.manifest_path
|
|
.parent()
|
|
.unwrap()
|
|
.as_std_path()
|
|
.to_path_buf()
|
|
.canonicalize()?;
|
|
|
|
let workspace_path = self
|
|
.krates
|
|
.workspace_root()
|
|
.as_std_path()
|
|
.to_path_buf()
|
|
.canonicalize()?;
|
|
|
|
let mut dioxus_conf_file = None;
|
|
while current_dir.starts_with(&workspace_path) {
|
|
let config = ["Dioxus.toml", "dioxus.toml"]
|
|
.into_iter()
|
|
.map(|file| current_dir.join(file))
|
|
.find(|path| path.is_file());
|
|
|
|
// Try to find Dioxus.toml in the current directory
|
|
if let Some(new_config) = config {
|
|
dioxus_conf_file = Some(new_config.as_path().to_path_buf());
|
|
break;
|
|
}
|
|
// If we can't find it, go up a directory
|
|
current_dir = current_dir
|
|
.parent()
|
|
.context("Failed to find Dioxus.toml")?
|
|
.to_path_buf();
|
|
}
|
|
|
|
// Load base config from Dioxus.toml (if it exists)
|
|
let base_config: Option<DioxusConfig> = match &dioxus_conf_file {
|
|
Some(path) => {
|
|
let content = std::fs::read_to_string(path)?;
|
|
Some(toml::from_str(&content).map_err(|err| {
|
|
anyhow::anyhow!("Failed to parse Dioxus.toml at {path:?}: {err}")
|
|
})?)
|
|
}
|
|
None => None,
|
|
};
|
|
|
|
// Extract inline config from source file (if provided)
|
|
let inline_config = source_file.and_then(crate::config::extract_inline_config_from_file);
|
|
|
|
// Merge configs: inline overrides base
|
|
match (base_config, inline_config) {
|
|
(Some(base), Some(inline)) => crate::config::merge_with_inline_config(&base, inline)
|
|
.map(Some)
|
|
.map_err(|err| anyhow::anyhow!("Failed to merge inline config: {err}")),
|
|
(Some(base), None) => Ok(Some(base)),
|
|
(None, Some(inline)) => {
|
|
// No Dioxus.toml, but we have inline config - use defaults + inline
|
|
let base = DioxusConfig::default();
|
|
crate::config::merge_with_inline_config(&base, inline)
|
|
.map(Some)
|
|
.map_err(|err| anyhow::anyhow!("Failed to merge inline config: {err}"))
|
|
}
|
|
(None, None) => Ok(None),
|
|
}
|
|
}
|
|
|
|
/// Create a new gitignore map for this target crate
|
|
///
|
|
/// todo(jon): this is a bit expensive to build, so maybe we should cache it?
|
|
pub fn workspace_gitignore(workspace_dir: &Path) -> Gitignore {
|
|
let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(workspace_dir);
|
|
ignore_builder.add(workspace_dir.join(".gitignore"));
|
|
|
|
for path in Self::default_ignore_list() {
|
|
ignore_builder
|
|
.add_line(None, path)
|
|
.expect("failed to add path to file excluded");
|
|
}
|
|
|
|
ignore_builder.build().unwrap()
|
|
}
|
|
|
|
pub fn ignore_for_krate(&self, path: &Path) -> ignore::gitignore::Gitignore {
|
|
let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(path);
|
|
for path in Self::default_ignore_list() {
|
|
ignore_builder
|
|
.add_line(None, path)
|
|
.expect("failed to add path to file excluded");
|
|
}
|
|
ignore_builder.build().unwrap()
|
|
}
|
|
|
|
pub fn default_ignore_list() -> Vec<&'static str> {
|
|
vec![
|
|
".git",
|
|
".github",
|
|
".vscode",
|
|
"target",
|
|
"node_modules",
|
|
"dist",
|
|
"*~",
|
|
".*",
|
|
"*.lock",
|
|
"*.log",
|
|
]
|
|
}
|
|
|
|
pub(crate) fn workspace_root(&self) -> PathBuf {
|
|
self.krates.workspace_root().as_std_path().to_path_buf()
|
|
}
|
|
|
|
/// Returns the root of the crate that the command is run from, without calling `cargo metadata`
|
|
///
|
|
/// If the command is run from the workspace root, this will return the top-level Cargo.toml
|
|
pub(crate) fn crate_root_from_path() -> Result<PathBuf> {
|
|
/// How many parent folders are searched for a `Cargo.toml`
|
|
const MAX_ANCESTORS: u32 = 10;
|
|
|
|
/// Checks if the directory contains `Cargo.toml`
|
|
fn contains_manifest(path: &Path) -> bool {
|
|
std::fs::read_dir(path)
|
|
.map(|entries| {
|
|
entries
|
|
.filter_map(Result::ok)
|
|
.any(|ent| &ent.file_name() == "Cargo.toml")
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
// From the current directory we work our way up, looking for `Cargo.toml`
|
|
std::env::current_dir()
|
|
.ok()
|
|
.and_then(|mut wd| {
|
|
for _ in 0..MAX_ANCESTORS {
|
|
if contains_manifest(&wd) {
|
|
return Some(wd);
|
|
}
|
|
if !wd.pop() {
|
|
break;
|
|
}
|
|
}
|
|
None
|
|
})
|
|
.context("Failed to find directory containing Cargo.toml")
|
|
}
|
|
|
|
pub async fn get_xcode_path() -> Option<PathBuf> {
|
|
let xcode = Command::new("xcode-select")
|
|
.arg("-p")
|
|
.output()
|
|
.await
|
|
.ok()
|
|
.map(|s| String::from_utf8_lossy(&s.stdout).trim().to_string().into());
|
|
xcode
|
|
}
|
|
|
|
pub async fn get_rustc_sysroot() -> Result<String, anyhow::Error> {
|
|
let sysroot = Command::new("rustc")
|
|
.args(["--print", "sysroot"])
|
|
.output()
|
|
.await
|
|
.map(|out| String::from_utf8(out.stdout).map(|s| s.trim().to_string()))?
|
|
.context("Failed to extract rustc sysroot output")?;
|
|
Ok(sysroot)
|
|
}
|
|
|
|
pub async fn get_rustc_version() -> Result<String> {
|
|
let rustc_version = Command::new("rustc")
|
|
.args(["--version"])
|
|
.output()
|
|
.await
|
|
.map(|out| String::from_utf8(out.stdout))?
|
|
.context("Failed to extract rustc version output")?;
|
|
Ok(rustc_version)
|
|
}
|
|
|
|
/// Returns the properly canonicalized path to the dx executable, used for linking and wrapping rustc
|
|
pub(crate) fn path_to_dx() -> Result<PathBuf> {
|
|
dunce::canonicalize(std::env::current_exe().context("Failed to find dx")?)
|
|
.context("Failed to find dx")
|
|
}
|
|
|
|
/// Returns the path to the dioxus data directory, used to install tools, store configs, and other things
|
|
///
|
|
/// On macOS, we prefer to not put this dir in Application Support, but rather in the home directory.
|
|
/// On Windows, we prefer to keep it in the home directory so the `dx` install dir matches the install script.
|
|
pub(crate) fn dioxus_data_dir() -> PathBuf {
|
|
static DX_HOME: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
|
|
DX_HOME
|
|
.get_or_init(|| {
|
|
if let Some(path) = std::env::var_os("DX_HOME") {
|
|
return PathBuf::from(path);
|
|
}
|
|
|
|
if cfg!(target_os = "macos") || cfg!(target_os = "windows") {
|
|
dirs::home_dir().unwrap().join(".dx")
|
|
} else {
|
|
dirs::data_dir()
|
|
.or_else(dirs::home_dir)
|
|
.unwrap()
|
|
.join(".dx")
|
|
}
|
|
})
|
|
.to_path_buf()
|
|
}
|
|
|
|
pub(crate) fn global_settings_file() -> PathBuf {
|
|
Self::dioxus_data_dir().join("settings.toml")
|
|
}
|
|
|
|
/// The path where components downloaded from git are cached
|
|
pub(crate) fn component_cache_dir() -> PathBuf {
|
|
Self::dioxus_data_dir().join("components")
|
|
}
|
|
|
|
/// Get the path to a specific component in the cache
|
|
pub(crate) fn component_cache_path(git: &str, rev: Option<&str>) -> PathBuf {
|
|
use std::hash::Hasher;
|
|
|
|
let mut hasher = std::hash::DefaultHasher::new();
|
|
std::hash::Hash::hash(git, &mut hasher);
|
|
if let Some(rev) = rev {
|
|
std::hash::Hash::hash(rev, &mut hasher);
|
|
}
|
|
let hash = hasher.finish();
|
|
Self::component_cache_dir().join(format!("{hash:016x}"))
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Workspace {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Workspace")
|
|
.field("krates", &"..")
|
|
.field("settings", &self.settings)
|
|
.field("rustc_version", &self.rustc_version)
|
|
.field("sysroot", &self.sysroot)
|
|
.field("wasm_opt", &self.wasm_opt)
|
|
.finish()
|
|
}
|
|
}
|