Fix bundling/path errors, sidecar support, iOS/Android simulator support, asset hotreload fixes, serverfn hotrelad, native tailwind support (#2779)
* wip: fix manganis import path * upgrade tauri bundler, convert todo!() to unimplemented!() * feat: mobile hotreloading + ios asset configuration * remove oid demo and its associated env vars * nuke all the things that cause cache thrashing * swap to Asset type * add some more logs * display impl for attrvalue * dont panic on collect failure * cut down web deps to 150 * clean up deps in a lot of places, simplify build scripts * clean up asset resolution and cli-dev profile * wire up absolute paths for manganis asset in a particular mode * move document related things around to shorten compile times * move most things to `document::Item` and then decompose the cli-config * switch link to stylesheet for clarity in examples * move manganis workspace example to examples folder * decompose manganis-cli-support * dont need mobile demo anymore * remove gloo dialogs * html doesnt need document * rename hotreload to devtools * really clean up the html crate * fix weird rsx spacing * clean up desktop to use callback * clean up document a bit * re-wire up devsocket * fix utf css * yeeeet that js out of here * synthetic web system works * web crate almost done being cleaned up * desktop mostly cleaned up too * clean up cli a bit * more cli cleanup * cli builds again * clean up cli, inline structs, cut down number of unique types where possible * hotreload works again for desktop and this time, mobile too * cfg out tungstenite * devserver err * more refactor to cli builder * switch to unbounded_send * new structure is much cleaner * add http serve subcommand * bundled hotreload * kick stylsheets * clean up a bit more, split up eventloop * amazingly serve seems done * change from pub to pub(crate) in cli * remove tools * bit more polish to cli * fix issue with join * gracefully handle fullstack without a server * fullstack mobile demo * fix launch function, move projects into example-projects folder * hoist examples * add "run" command * clean up launch * remove old manual websocket receiver * doctor command * allow desktop to scroll * cut apart router crate * dont put launch in prelude * use dioxus::launch where possible * rename rsx, cut out hotreload tests * remove liveview project * bump native * fix compile for renderers * move sync event response out of interpreter * move render in serve * rollback settings change * cli compiles, huzzah! * change uris for asset * fix asset * new tui screen * new cargo-like tui works * very very verty close * it works! very small bug with incorrect grapheme calc * Clean up devserver a bit * status sytem * tidy up debug filters * clean up logging situation * Fix a number of bugs with log printing * new printing system is more reliable * wire up more stuff * things working but fullstack is having issues * fullstack works again! * hotreloading bundled assets works again * bundled hotreload and beginnings of macos bundling * Hotreload desktop * combined server builds * add build handle * fix fullstack assets * make open async, add some hooks for ios * migrate filemap to runner * wip global crate system * fixup bundles + organize asset * fix asset location bug * all the bundled reloading! * open ios simulator! * full hotreload support for mobile + serverfn * basic cleanups * clean up dx * Move filemap * fix cutting of newlines * assets workibg, some android * hoist wry/tao * use sync locks and headers to fix issues with android * desktop -> mac/win/linux with alias * better logging * feat: workspace (entire computer!) hotreload * should rebuild toggle, ios simulator bootup * proper mobile support in launch * more robust handling of assets * fix cargo * bring back some of tauri bundle * make warnings go away, clippy happy on cli * some final clippy cleanups * fmt * move manganis to its own folder * upgrade bundle to stable * drastically slim down manganis, prepping for merge * typos, failing test, docsrs config * remove static gen test * nix static gen test * we use --platform web instead of --platform fullstack now * only bind dev urls in desktop/mobile * install gtk * nix static gen * split build dir by app name
This commit is contained in:
1 parent
ef436e4ed0
commit
7ec3453ca3
156 files changed
+11544
-9005
No files matched your search
+547
-291
@@ -1,86 +1,567 @@
|
||||
use crate::build::TargetArgs;
|
||||
use crate::config::{DioxusConfig, Platform};
|
||||
use crate::CliSettings;
|
||||
use crate::{config::DioxusConfig, TargetArgs};
|
||||
use crate::{Platform, Result};
|
||||
use anyhow::Context;
|
||||
use krates::{cm::Target, KrateDetails};
|
||||
use krates::{cm::TargetKind, Cmd, Krates, NodeId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::{io::Write, path::Path};
|
||||
use toml_edit::Item;
|
||||
|
||||
use crate::metadata::CargoError;
|
||||
// Contains information about the crate we are currently in and the dioxus config for that crate
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DioxusCrate {
|
||||
pub(crate) krates: Arc<Krates>,
|
||||
pub(crate) package: NodeId,
|
||||
pub(crate) config: DioxusConfig,
|
||||
pub(crate) target: Target,
|
||||
pub(crate) settings: CliSettings,
|
||||
}
|
||||
|
||||
/// Load the dioxus config from a path
|
||||
fn load_dioxus_config(
|
||||
krates: &Krates,
|
||||
package: NodeId,
|
||||
) -> Result<Option<DioxusConfig>, CrateConfigError> {
|
||||
fn acquire_dioxus_toml(dir: &std::path::Path) -> Option<PathBuf> {
|
||||
["Dioxus.toml", "dioxus.toml"]
|
||||
.into_iter()
|
||||
.map(|file| dir.join(file))
|
||||
.find(|path| path.is_file())
|
||||
}
|
||||
pub(crate) static PROFILE_WASM: &str = "dioxus-wasm";
|
||||
pub(crate) static PROFILE_ANDROID: &str = "dioxus-android";
|
||||
pub(crate) static PROFILE_SERVER: &str = "dioxus-server";
|
||||
|
||||
// Walk up from the cargo.toml to the root of the workspace looking for Dioxus.toml
|
||||
let mut current_dir = krates[package]
|
||||
.manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.as_std_path()
|
||||
.to_path_buf()
|
||||
.canonicalize()?;
|
||||
let workspace_path = krates
|
||||
.workspace_root()
|
||||
.as_std_path()
|
||||
.to_path_buf()
|
||||
.canonicalize()?;
|
||||
impl DioxusCrate {
|
||||
pub(crate) fn new(target: &TargetArgs) -> Result<Self> {
|
||||
let mut cmd = Cmd::new();
|
||||
cmd.features(target.features.clone());
|
||||
|
||||
let mut dioxus_conf_file = None;
|
||||
while current_dir.starts_with(&workspace_path) {
|
||||
// Try to find Dioxus.toml in the current directory
|
||||
if let Some(new_config) = acquire_dioxus_toml(¤t_dir) {
|
||||
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()
|
||||
.ok_or(CrateConfigError::CurrentPackageNotFound)?
|
||||
.to_path_buf();
|
||||
}
|
||||
let krates = krates::Builder::new()
|
||||
.build(cmd, |_| {})
|
||||
.context("Failed to run cargo metadata")?;
|
||||
|
||||
let Some(dioxus_conf_file) = dioxus_conf_file else {
|
||||
return Ok(None);
|
||||
};
|
||||
let package = find_main_package(&krates, target.package.clone())?;
|
||||
|
||||
let cfg = toml::from_str::<DioxusConfig>(&std::fs::read_to_string(&dioxus_conf_file)?)
|
||||
.map_err(|err| {
|
||||
CrateConfigError::LoadDioxusConfig(LoadDioxusConfigError {
|
||||
location: dioxus_conf_file.display().to_string(),
|
||||
error: err.to_string(),
|
||||
let dioxus_config = DioxusConfig::load(&krates, package)?.unwrap_or_default();
|
||||
|
||||
let package_name = krates[package].name.clone();
|
||||
let target_kind = if target.example.is_some() {
|
||||
TargetKind::Example
|
||||
} else {
|
||||
TargetKind::Bin
|
||||
};
|
||||
|
||||
let target_name = target
|
||||
.example
|
||||
.clone()
|
||||
.or(target.bin.clone())
|
||||
.unwrap_or(package_name);
|
||||
|
||||
let main_package = &krates[package];
|
||||
let target = main_package
|
||||
.targets
|
||||
.iter()
|
||||
.find(|target| {
|
||||
target_name == target.name.as_str() && target.kind.contains(&target_kind)
|
||||
})
|
||||
.with_context(|| format!("Failed to find target {target_name}"))?
|
||||
.clone();
|
||||
|
||||
let settings = CliSettings::load();
|
||||
|
||||
Ok(Self {
|
||||
krates: Arc::new(krates),
|
||||
package,
|
||||
config: dioxus_config,
|
||||
target,
|
||||
settings,
|
||||
})
|
||||
.map(Some);
|
||||
match cfg {
|
||||
Ok(Some(mut cfg)) => {
|
||||
let name = cfg.application.name.clone();
|
||||
if cfg.bundle.identifier.is_none() {
|
||||
cfg.bundle.identifier = Some(format!("io.github.{name}"));
|
||||
}
|
||||
|
||||
/// Compose an asset directory. Represents the typical "public" directory
|
||||
/// with publicly available resources (configurable in the `Dioxus.toml`).
|
||||
pub(crate) fn legacy_asset_dir(&self) -> PathBuf {
|
||||
self.crate_dir().join(&self.config.application.asset_dir)
|
||||
}
|
||||
|
||||
/// Get the list of files in the "legacy" asset directory
|
||||
pub(crate) fn legacy_asset_dir_files(&self) -> Vec<PathBuf> {
|
||||
let mut files = vec![];
|
||||
|
||||
let Ok(read_dir) = self.legacy_asset_dir().read_dir() else {
|
||||
return files;
|
||||
};
|
||||
|
||||
for entry in read_dir.flatten() {
|
||||
files.push(entry.path());
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
/// Compose an out directory. Represents the typical "dist" directory that
|
||||
/// is "distributed" after building an application (configurable in the
|
||||
/// `Dioxus.toml`).
|
||||
fn out_dir(&self) -> PathBuf {
|
||||
let dir = self.workspace_dir().join("target").join("dx");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
/// Create a workdir for the given platform
|
||||
/// This can be used as a temporary directory for the build, but in an observable way such that
|
||||
/// you can see the files in the directory via `target`
|
||||
///
|
||||
/// target/dx/build/app/web/
|
||||
/// target/dx/build/app/web/public/
|
||||
/// target/dx/build/app/web/server.exe
|
||||
pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf {
|
||||
self.out_dir()
|
||||
.join(self.executable_name())
|
||||
.join(if release { "release" } else { "debug" })
|
||||
.join(platform.build_folder_name())
|
||||
}
|
||||
|
||||
/// target/dx/bundle/app/
|
||||
/// target/dx/bundle/app/blah.app
|
||||
/// target/dx/bundle/app/blah.exe
|
||||
/// target/dx/bundle/app/public/
|
||||
pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
|
||||
self.out_dir()
|
||||
.join(self.executable_name())
|
||||
.join("bundle")
|
||||
.join(platform.build_folder_name())
|
||||
}
|
||||
|
||||
/// Get the workspace directory for the crate
|
||||
pub(crate) fn workspace_dir(&self) -> PathBuf {
|
||||
self.krates.workspace_root().as_std_path().to_path_buf()
|
||||
}
|
||||
|
||||
/// Get the directory of the crate
|
||||
pub(crate) fn crate_dir(&self) -> PathBuf {
|
||||
self.package()
|
||||
.manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.as_std_path()
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
/// Get the main source file of the target
|
||||
pub(crate) fn main_source_file(&self) -> PathBuf {
|
||||
self.target.src_path.as_std_path().to_path_buf()
|
||||
}
|
||||
|
||||
/// Get the package we are currently in
|
||||
pub(crate) fn package(&self) -> &krates::cm::Package {
|
||||
&self.krates[self.package]
|
||||
}
|
||||
|
||||
/// Get the name of the package we are compiling
|
||||
pub(crate) fn executable_name(&self) -> &str {
|
||||
&self.target.name
|
||||
}
|
||||
|
||||
/// Get the type of executable we are compiling
|
||||
pub(crate) fn executable_type(&self) -> krates::cm::TargetKind {
|
||||
self.target.kind[0].clone()
|
||||
}
|
||||
|
||||
/// Try to autodetect the platform from the package by reading its features
|
||||
///
|
||||
/// Read the default-features list and/or the features list on dioxus to see if we can autodetect the platform
|
||||
pub(crate) fn autodetect_platform(&self) -> Option<(Platform, String)> {
|
||||
let krate = self.krates.krates_by_name("dioxus").next()?;
|
||||
|
||||
// We're going to accumulate the platforms that are enabled
|
||||
// This will let us create a better warning if multiple platforms are enabled
|
||||
let manually_enabled_platforms = self
|
||||
.krates
|
||||
.get_enabled_features(krate.kid)?
|
||||
.iter()
|
||||
.flat_map(|feature| {
|
||||
Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if manually_enabled_platforms.len() > 1 {
|
||||
tracing::error!("Multiple platforms are enabled. Please specify a platform with `--platform <platform>` or set a single default platform using a cargo feature.");
|
||||
for platform in manually_enabled_platforms {
|
||||
tracing::error!(" - {platform:?}");
|
||||
}
|
||||
if cfg.bundle.publisher.is_none() {
|
||||
cfg.bundle.publisher = Some(name);
|
||||
return None;
|
||||
}
|
||||
|
||||
if manually_enabled_platforms.len() == 1 {
|
||||
return manually_enabled_platforms.first().cloned();
|
||||
}
|
||||
|
||||
// Let's try and find the list of platforms from the feature list
|
||||
// This lets apps that specify web + server to work without specifying the platform.
|
||||
// This is because we treat `server` as a binary thing rather than a dedicated platform, so at least we can disambiguate it
|
||||
let possible_platforms = self
|
||||
.package()
|
||||
.features
|
||||
.iter()
|
||||
.filter_map(|(feature, _features)| {
|
||||
match Platform::autodetect_from_cargo_feature(feature) {
|
||||
Some(platform) => Some((platform, feature.to_string())),
|
||||
None => {
|
||||
let auto_implicit = _features
|
||||
.iter()
|
||||
.filter_map(|f| {
|
||||
if !f.starts_with("dioxus?/") && !f.starts_with("dioxus/") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rest = f
|
||||
.trim_start_matches("dioxus/")
|
||||
.trim_start_matches("dioxus?/");
|
||||
|
||||
Platform::autodetect_from_cargo_feature(rest)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if auto_implicit.len() == 1 {
|
||||
Some((auto_implicit.first().copied().unwrap(), feature.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(|platform| platform.0 != Platform::Server)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if possible_platforms.len() == 1 {
|
||||
return possible_platforms.first().cloned();
|
||||
}
|
||||
|
||||
tracing::warn!("Could not autodetect platform. Platform must be explicitly specified. Pass `--platform <platform>` or set a default platform using a cargo feature.");
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if dioxus is being built with a particular feature
|
||||
pub(crate) fn has_dioxus_feature(&self, filter: &str) -> bool {
|
||||
self.krates.krates_by_name("dioxus").any(|dioxus| {
|
||||
self.krates
|
||||
.get_enabled_features(dioxus.kid)
|
||||
.map(|features| features.contains(filter))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the features required to build for the given platform
|
||||
pub(crate) fn feature_for_platform(&self, platform: Platform) -> Option<String> {
|
||||
let package = self.package();
|
||||
|
||||
// Try to find the feature that activates the dioxus feature for the given platform
|
||||
let dioxus_feature = platform.feature_name();
|
||||
|
||||
package.features.iter().find_map(|(key, features)| {
|
||||
// if the feature is just the name of the platform, we use that
|
||||
if key == dioxus_feature {
|
||||
return Some(key.clone());
|
||||
}
|
||||
|
||||
Ok(Some(cfg))
|
||||
// Otherwise look for the feature that starts with dioxus/ or dioxus?/ and matches the platform
|
||||
for feature in features {
|
||||
if let Some((_, after_dioxus)) = feature.split_once("dioxus") {
|
||||
if let Some(dioxus_feature_enabled) =
|
||||
after_dioxus.trim_start_matches('?').strip_prefix('/')
|
||||
{
|
||||
// If that enables the feature we are looking for, return that feature
|
||||
if dioxus_feature_enabled == dioxus_feature {
|
||||
return Some(key.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if assets should be pre_compressed. This will only be true in release mode if the user
|
||||
/// has enabled pre_compress in the web config.
|
||||
pub(crate) fn should_pre_compress_web_assets(&self, release: bool) -> bool {
|
||||
self.config.web.pre_compress && release
|
||||
}
|
||||
|
||||
// The `opt-level=2` increases build times, but can noticeably decrease time
|
||||
// between saving changes and being able to interact with an app (for wasm/web). The "overall"
|
||||
// time difference (between having and not having the optimization) can be
|
||||
// almost imperceptible (~1 s) but also can be very noticeable (~6 s) — depends
|
||||
// on setup (hardware, OS, browser, idle load).
|
||||
//
|
||||
// Find or create the client and server profiles in the .cargo/config.toml file
|
||||
pub(crate) fn initialize_profiles(&self) -> crate::Result<()> {
|
||||
let config_path = self.workspace_dir().join(".cargo/config.toml");
|
||||
let mut config = match std::fs::read_to_string(&config_path) {
|
||||
Ok(config) => config.parse::<toml_edit::DocumentMut>().map_err(|e| {
|
||||
crate::Error::Other(anyhow::anyhow!("Failed to parse .cargo/config.toml: {}", e))
|
||||
})?,
|
||||
Err(_) => Default::default(),
|
||||
};
|
||||
|
||||
if let Item::Table(table) = config
|
||||
.as_table_mut()
|
||||
.entry("profile")
|
||||
.or_insert(Item::Table(Default::default()))
|
||||
{
|
||||
if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_WASM) {
|
||||
let mut client = toml_edit::Table::new();
|
||||
client.insert("inherits", Item::Value("dev".into()));
|
||||
client.insert("opt-level", Item::Value(2.into()));
|
||||
entry.insert(Item::Table(client));
|
||||
}
|
||||
|
||||
if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_SERVER) {
|
||||
let mut server = toml_edit::Table::new();
|
||||
server.insert("inherits", Item::Value("dev".into()));
|
||||
server.insert("opt-level", Item::Value(2.into()));
|
||||
entry.insert(Item::Table(server));
|
||||
}
|
||||
|
||||
if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_ANDROID) {
|
||||
let mut android = toml_edit::Table::new();
|
||||
android.insert("inherits", Item::Value("dev".into()));
|
||||
android.insert("opt-level", Item::Value(2.into()));
|
||||
entry.insert(Item::Table(android));
|
||||
}
|
||||
}
|
||||
cfg => cfg,
|
||||
|
||||
// Write the config back to the file
|
||||
if let Some(parent) = config_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let file = std::fs::File::create(config_path)?;
|
||||
let mut buf_writer = std::io::BufWriter::new(file);
|
||||
write!(buf_writer, "{}", config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_ignore_list(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
".git",
|
||||
".github",
|
||||
".vscode",
|
||||
"target",
|
||||
"node_modules",
|
||||
"dist",
|
||||
"*~",
|
||||
".*",
|
||||
"*.lock",
|
||||
"*.log",
|
||||
"*.rs",
|
||||
]
|
||||
}
|
||||
|
||||
/// 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(&self) -> ignore::gitignore::Gitignore {
|
||||
let crate_dir = self.crate_dir();
|
||||
|
||||
let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(&crate_dir);
|
||||
ignore_builder.add(crate_dir.join(".gitignore"));
|
||||
|
||||
let workspace_dir = self.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 excluder");
|
||||
}
|
||||
|
||||
ignore_builder.build().unwrap()
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
pub(crate) fn default_platform(&self) -> Option<Platform> {
|
||||
let default = self.package().features.get("default")?;
|
||||
|
||||
// we only trace features 1 level deep..
|
||||
for feature in default.iter() {
|
||||
// If the user directly specified a platform we can just use that.
|
||||
if feature.starts_with("dioxus/") {
|
||||
let dx_feature = feature.trim_start_matches("dioxus/");
|
||||
let auto = Platform::autodetect_from_cargo_feature(dx_feature);
|
||||
if auto.is_some() {
|
||||
return auto;
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is specifying an internal feature that points to a platform, we can use that
|
||||
let internal_feature = self.package().features.get(feature);
|
||||
if let Some(internal_feature) = internal_feature {
|
||||
for feature in internal_feature {
|
||||
if feature.starts_with("dioxus/") {
|
||||
let dx_feature = feature.trim_start_matches("dioxus/");
|
||||
let auto = Platform::autodetect_from_cargo_feature(dx_feature);
|
||||
if auto.is_some() {
|
||||
return auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Gather the features that are enabled for the package
|
||||
pub(crate) fn platformless_features(&self) -> Vec<String> {
|
||||
let default = self.package().features.get("default").unwrap();
|
||||
let mut kept_features = vec![];
|
||||
|
||||
// Only keep the top-level features in the default list that don't point to a platform directly
|
||||
// IE we want to drop `web` if default = ["web"]
|
||||
'top: for feature in default {
|
||||
// Don't keep features that point to a platform via dioxus/blah
|
||||
if feature.starts_with("dioxus/") {
|
||||
let dx_feature = feature.trim_start_matches("dioxus/");
|
||||
if Platform::autodetect_from_cargo_feature(dx_feature).is_some() {
|
||||
continue 'top;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't keep features that point to a platform via an internal feature
|
||||
if let Some(internal_feature) = self.package().features.get(feature) {
|
||||
for feature in internal_feature {
|
||||
if feature.starts_with("dioxus/") {
|
||||
let dx_feature = feature.trim_start_matches("dioxus/");
|
||||
if Platform::autodetect_from_cargo_feature(dx_feature).is_some() {
|
||||
continue 'top;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we can keep it
|
||||
kept_features.push(feature.to_string());
|
||||
}
|
||||
|
||||
kept_features
|
||||
}
|
||||
|
||||
/// Return the list of paths that we should watch for changes.
|
||||
pub(crate) fn watch_paths(&self) -> Vec<PathBuf> {
|
||||
let mut watched_paths = vec![];
|
||||
|
||||
// Get a list of *all* the crates with Rust code that we need to watch.
|
||||
// This will end up being dependencies in the workspace and non-workspace dependencies on the user's computer.
|
||||
let mut watched_crates = self.local_dependencies();
|
||||
watched_crates.push(self.crate_dir());
|
||||
|
||||
// Now, watch all the folders in the crates, but respecting their respective ignore files
|
||||
for krate_root in watched_crates {
|
||||
// Build the ignore builder for this crate, but with our default ignore list as well
|
||||
let ignore = self.ignore_for_krate(&krate_root);
|
||||
|
||||
for entry in krate_root.read_dir().unwrap() {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if ignore
|
||||
.matched(entry.path(), entry.path().is_dir())
|
||||
.is_ignore()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
watched_paths.push(entry.path().to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
watched_paths.dedup();
|
||||
|
||||
watched_paths
|
||||
}
|
||||
|
||||
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 excluder");
|
||||
}
|
||||
ignore_builder.build().unwrap()
|
||||
}
|
||||
|
||||
/// Get all the Manifest paths for dependencies that we should watch. Will not return anything
|
||||
/// in the `.cargo` folder - only local dependencies will be watched.
|
||||
///
|
||||
/// This returns a list of manifest paths
|
||||
///
|
||||
/// Extend the watch path to include:
|
||||
///
|
||||
/// - the assets directory - this is so we can hotreload CSS and other assets by default
|
||||
/// - the Cargo.toml file - this is so we can hotreload the project if the user changes dependencies
|
||||
/// - the Dioxus.toml file - this is so we can hotreload the project if the user changes the Dioxus config
|
||||
pub(crate) fn local_dependencies(&self) -> Vec<PathBuf> {
|
||||
let mut paths = vec![];
|
||||
|
||||
for (dependency, _edge) in self.krates.get_deps(self.package) {
|
||||
let krate = match dependency {
|
||||
krates::Node::Krate { krate, .. } => krate,
|
||||
krates::Node::Feature { krate_index, .. } => &self.krates[krate_index.index()],
|
||||
};
|
||||
|
||||
if krate
|
||||
.manifest_path
|
||||
.components()
|
||||
.any(|c| c.as_str() == ".cargo")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
paths.push(
|
||||
krate
|
||||
.manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.to_path_buf()
|
||||
.into_std_path_buf(),
|
||||
);
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
pub(crate) fn all_watched_crates(&self) -> Vec<PathBuf> {
|
||||
let mut krates: Vec<PathBuf> = self
|
||||
.local_dependencies()
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
p.parent()
|
||||
.expect("Local manifest to exist and have a parent")
|
||||
.to_path_buf()
|
||||
})
|
||||
.chain(Some(self.crate_dir()))
|
||||
.collect();
|
||||
|
||||
krates.dedup();
|
||||
|
||||
krates
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DioxusCrate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DioxusCrate")
|
||||
.field("package", &self.krates[self.package])
|
||||
.field("dioxus_config", &self.config)
|
||||
.field("target", &self.target)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// Find the main package in the workspace
|
||||
fn find_main_package(package: Option<String>, krates: &Krates) -> Result<NodeId, CrateConfigError> {
|
||||
fn find_main_package(krates: &Krates, package: Option<String>) -> Result<NodeId> {
|
||||
let kid = match package {
|
||||
Some(package) => {
|
||||
let mut workspace_members = krates.workspace_members();
|
||||
@@ -103,7 +584,7 @@ fn find_main_package(package: Option<String>, krates: &Krates) -> Result<NodeId,
|
||||
}
|
||||
}
|
||||
|
||||
found.ok_or_else(|| CrateConfigError::PackageNotFound(package.clone()))?
|
||||
found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?
|
||||
}
|
||||
None => {
|
||||
// Otherwise find the package that is the closest parent of the current directory
|
||||
@@ -131,235 +612,10 @@ fn find_main_package(package: Option<String>, krates: &Krates) -> Result<NodeId,
|
||||
}
|
||||
closest_parent
|
||||
.map(|(id, _)| id)
|
||||
.ok_or(CrateConfigError::CurrentPackageNotFound)?
|
||||
.context("Failed to find current package")?
|
||||
}
|
||||
};
|
||||
|
||||
let package = krates.nid_for_kid(kid).unwrap();
|
||||
Ok(package)
|
||||
}
|
||||
|
||||
// Contains information about the crate we are currently in and the dioxus config for that crate
|
||||
#[derive(Clone)]
|
||||
pub struct DioxusCrate {
|
||||
pub krates: Arc<Krates>,
|
||||
pub package: NodeId,
|
||||
pub dioxus_config: DioxusConfig,
|
||||
pub target: Target,
|
||||
}
|
||||
|
||||
impl DioxusCrate {
|
||||
pub fn new(target: &TargetArgs) -> Result<Self, CrateConfigError> {
|
||||
let mut cmd = Cmd::new();
|
||||
cmd.features(target.features.clone());
|
||||
let builder = krates::Builder::new();
|
||||
let krates = builder.build(cmd, |_| {})?;
|
||||
|
||||
let package = find_main_package(target.package.clone(), &krates)?;
|
||||
|
||||
let dioxus_config = load_dioxus_config(&krates, package)?.unwrap_or_default();
|
||||
|
||||
let package_name = krates[package].name.clone();
|
||||
let target_kind = if target.example.is_some() {
|
||||
TargetKind::Example
|
||||
} else {
|
||||
TargetKind::Bin
|
||||
};
|
||||
let target_name = target
|
||||
.example
|
||||
.clone()
|
||||
.or(target.bin.clone())
|
||||
.unwrap_or(package_name);
|
||||
let main_package = &krates[package];
|
||||
let target = main_package
|
||||
.targets
|
||||
.iter()
|
||||
.find(|target| {
|
||||
target_name == target.name.as_str() && target.kind.contains(&target_kind)
|
||||
})
|
||||
.ok_or(CrateConfigError::TargetNotFound(target_name))?
|
||||
.clone();
|
||||
|
||||
Ok(Self {
|
||||
krates: Arc::new(krates),
|
||||
package,
|
||||
dioxus_config,
|
||||
target,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compose an asset directory. Represents the typical "public" directory
|
||||
/// with publicly available resources (configurable in the `Dioxus.toml`).
|
||||
pub fn asset_dir(&self) -> PathBuf {
|
||||
self.crate_dir()
|
||||
.join(&self.dioxus_config.application.asset_dir)
|
||||
}
|
||||
|
||||
/// Compose an out directory. Represents the typical "dist" directory that
|
||||
/// is "distributed" after building an application (configurable in the
|
||||
/// `Dioxus.toml`).
|
||||
pub fn out_dir(&self) -> PathBuf {
|
||||
self.workspace_dir()
|
||||
.join(&self.dioxus_config.application.out_dir)
|
||||
}
|
||||
|
||||
/// Get the workspace directory for the crate
|
||||
pub fn workspace_dir(&self) -> PathBuf {
|
||||
self.krates.workspace_root().as_std_path().to_path_buf()
|
||||
}
|
||||
|
||||
/// Get the directory of the crate
|
||||
pub fn crate_dir(&self) -> PathBuf {
|
||||
self.package()
|
||||
.manifest_path
|
||||
.parent()
|
||||
.unwrap()
|
||||
.as_std_path()
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
/// Get the main source file of the target
|
||||
pub fn main_source_file(&self) -> PathBuf {
|
||||
self.target.src_path.as_std_path().to_path_buf()
|
||||
}
|
||||
|
||||
/// Get the package we are currently in
|
||||
pub fn package(&self) -> &krates::cm::Package {
|
||||
&self.krates[self.package]
|
||||
}
|
||||
|
||||
/// Get the name of the package we are compiling
|
||||
pub fn executable_name(&self) -> &str {
|
||||
&self.target.name
|
||||
}
|
||||
|
||||
/// Get the type of executable we are compiling
|
||||
pub fn executable_type(&self) -> krates::cm::TargetKind {
|
||||
self.target.kind[0].clone()
|
||||
}
|
||||
|
||||
pub fn features_for_platform(&mut self, platform: Platform) -> Vec<String> {
|
||||
let package = self.package();
|
||||
// Try to find the feature that activates the dioxus feature for the given platform
|
||||
let dioxus_feature = platform.feature_name();
|
||||
let feature = package.features.iter().find_map(|(key, features)| {
|
||||
// Find a feature that starts with dioxus/ or dioxus?/
|
||||
for feature in features {
|
||||
if let Some((_, after_dioxus)) = feature.split_once("dioxus") {
|
||||
if let Some(dioxus_feature_enabled) =
|
||||
after_dioxus.trim_start_matches('?').strip_prefix('/')
|
||||
{
|
||||
// If that enables the feature we are looking for, return that feature
|
||||
if dioxus_feature_enabled == dioxus_feature {
|
||||
return Some(key.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
feature.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Check if assets should be pre_compressed. This will only be true in release mode if the user has enabled pre_compress in the web config.
|
||||
pub fn should_pre_compress_web_assets(&self, release: bool) -> bool {
|
||||
self.dioxus_config.web.pre_compress && release
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Executable {
|
||||
pub name: String,
|
||||
pub ty: ExecutableType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum ExecutableType {
|
||||
Binary,
|
||||
Lib,
|
||||
Example,
|
||||
}
|
||||
|
||||
impl ExecutableType {
|
||||
/// Get the name of the executable if it is a binary or an example.
|
||||
pub fn executable(&self) -> bool {
|
||||
matches!(self, Self::Binary | Self::Example)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoadDioxusConfigError {
|
||||
location: String,
|
||||
error: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoadDioxusConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.location, self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LoadDioxusConfigError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum CrateConfigError {
|
||||
Cargo(CargoError),
|
||||
Io(std::io::Error),
|
||||
Toml(toml::de::Error),
|
||||
LoadDioxusConfig(LoadDioxusConfigError),
|
||||
TargetNotFound(String),
|
||||
Krates(krates::Error),
|
||||
PackageNotFound(String),
|
||||
CurrentPackageNotFound,
|
||||
}
|
||||
|
||||
impl From<CargoError> for CrateConfigError {
|
||||
fn from(err: CargoError) -> Self {
|
||||
Self::Cargo(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for CrateConfigError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for CrateConfigError {
|
||||
fn from(err: toml::de::Error) -> Self {
|
||||
Self::Toml(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadDioxusConfigError> for CrateConfigError {
|
||||
fn from(err: LoadDioxusConfigError) -> Self {
|
||||
Self::LoadDioxusConfig(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<krates::Error> for CrateConfigError {
|
||||
fn from(err: krates::Error) -> Self {
|
||||
Self::Krates(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CrateConfigError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Cargo(err) => write!(f, "{}", err),
|
||||
Self::Io(err) => write!(f, "{}", err),
|
||||
Self::Toml(err) => write!(f, "{}", err),
|
||||
Self::LoadDioxusConfig(err) => write!(f, "{}", err),
|
||||
Self::TargetNotFound(target) => {
|
||||
write!(f, "Failed to find target with name: {}", target)
|
||||
}
|
||||
Self::Krates(err) => write!(f, "{}", err),
|
||||
Self::PackageNotFound(package) => write!(f, "Package not found: {}", package),
|
||||
Self::CurrentPackageNotFound => write!(f, "Failed to find current package"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CrateConfigError {}
|
||||
Reference in new issue
Block a user