فهرست منبع

Fix outdir not work when run dx bundle --platform web --outdir mydir (#3572)

* Make legacy asset dir optional
* properly implement outdir

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
Co-authored-by:  Nathy-bajo <bajon7680@gmail.com>
BleemIs42 5 ماه پیش
والد
کامیت
19d5fae12d

+ 35 - 0
Cargo.lock

@@ -2855,6 +2855,19 @@ dependencies = [
  "itertools 0.10.5",
 ]
 
+[[package]]
+name = "crossbeam"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "crossbeam-channel"
 version = "0.5.13"
@@ -3478,6 +3491,7 @@ dependencies = [
  "dioxus-rsx",
  "dioxus-rsx-hotreload",
  "dioxus-rsx-rosetta",
+ "dircpy",
  "dirs",
  "dunce",
  "env_logger 0.11.5",
@@ -4242,6 +4256,17 @@ dependencies = [
  "tower-http",
 ]
 
+[[package]]
+name = "dircpy"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a88521b0517f5f9d51d11925d8ab4523497dcf947073fa3231a311b63941131c"
+dependencies = [
+ "jwalk",
+ "log",
+ "walkdir",
+]
+
 [[package]]
 name = "dirs"
 version = "5.0.1"
@@ -7206,6 +7231,16 @@ dependencies = [
  "simple_asn1",
 ]
 
+[[package]]
+name = "jwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56"
+dependencies = [
+ "crossbeam",
+ "rayon",
+]
+
 [[package]]
 name = "jzon"
 version = "0.12.5"

+ 1 - 0
packages/cli/Cargo.toml

@@ -119,6 +119,7 @@ tauri-bundler = { workspace = true }
 include_dir = "0.7.4"
 flate2 = "1.0.35"
 tar = "0.4.43"
+dircpy = "0.3.19"
 
 [build-dependencies]
 built = { version = "=0.7.4", features = ["git2"] }

+ 7 - 3
packages/cli/src/build/web.rs

@@ -154,8 +154,12 @@ impl BuildRequest {
                     // If the path is absolute, make it relative to the current directory before we join it
                     // The path is actually a web path which is relative to the root of the website
                     let path = path.strip_prefix("/").unwrap_or(path);
-                    let asset_dir_path = self.krate.legacy_asset_dir().join(path);
-                    if let Ok(absolute_path) = asset_dir_path.canonicalize() {
+                    let asset_dir_path = self
+                        .krate
+                        .legacy_asset_dir()
+                        .map(|dir| dir.join(path).canonicalize());
+
+                    if let Some(Ok(absolute_path)) = asset_dir_path {
                         let absolute_crate_root = self.krate.crate_dir().canonicalize().unwrap();
                         PathBuf::from("./")
                             .join(absolute_path.strip_prefix(absolute_crate_root).unwrap())
@@ -179,7 +183,7 @@ impl BuildRequest {
             ResourceType::Script => "web.resource.script",
         };
 
-        tracing::debug!(
+        tracing::warn!(
             "{RESOURCE_DEPRECATION_MESSAGE}\nTo migrate to head components, remove `{section_name}` and include the following rsx in your root component:\n```rust\n{replacement_components}\n```"
         );
     }

+ 65 - 56
packages/cli/src/cli/bundle.rs

@@ -1,5 +1,6 @@
 use crate::{AppBundle, BuildArgs, Builder, DioxusCrate, Platform};
 use anyhow::Context;
+use path_absolutize::Absolutize;
 use std::collections::HashMap;
 use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
 
@@ -25,12 +26,13 @@ pub struct Bundle {
 
     /// The directory in which the final bundle will be placed.
     ///
-    /// Relative paths will be placed relative to the current working directory.
+    /// Relative paths will be placed relative to the current working directory if specified.
+    /// Otherwise, the out_dir path specified in Dioxus.toml will be used (relative to the crate root).
     ///
     /// We will flatten the artifacts into this directory - there will be no differentiation between
     /// artifacts produced by different platforms.
     #[clap(long)]
-    pub outdir: Option<PathBuf>,
+    pub out_dir: Option<PathBuf>,
 
     /// The arguments for the dioxus build
     #[clap(flatten)]
@@ -54,72 +56,38 @@ impl Bundle {
             .finish()
             .await?;
 
-        tracing::info!("Copying app to output directory...");
-
         // If we're building for iOS, we need to bundle the iOS bundle
         if self.build_arguments.platform() == Platform::Ios && self.package_types.is_none() {
             self.package_types = Some(vec![crate::PackageType::IosBundle]);
         }
 
-        let mut cmd_result = StructuredOutput::Success;
+        let mut bundles = vec![];
+
+        // Copy the server over if it exists
+        if bundle.build.build.fullstack {
+            bundles.push(bundle.server_exe().unwrap());
+        }
 
+        // Create a list of bundles that we might need to copy
         match self.build_arguments.platform() {
             // By default, mac/win/linux work with tauri bundle
             Platform::MacOS | Platform::Linux | Platform::Windows => {
-                let bundles = self.bundle_desktop(krate, bundle)?;
-
-                tracing::info!("Bundled app successfully!");
-                tracing::info!("App produced {} outputs:", bundles.len());
-                tracing::debug!("Bundling produced bundles: {:#?}", bundles);
-
-                // Copy the bundles to the output directory and log their locations
-                let mut bundle_paths = vec![];
-                for bundle in bundles {
-                    for src in bundle.bundle_paths {
-                        let src = if let Some(outdir) = &self.outdir {
-                            let dest = outdir.join(src.file_name().expect("Filename to exist"));
-                            crate::fastfs::copy_asset(&src, &dest)
-                                .context("Failed to copy or compress optimized asset")?;
-                            dest
-                        } else {
-                            src.clone()
-                        };
-
-                        tracing::info!(
-                            "{} - [{}]",
-                            bundle.package_type.short_name(),
-                            src.display()
-                        );
-
-                        bundle_paths.push(src);
-                    }
+                tracing::info!("Running desktop bundler...");
+                for bundle in self.bundle_desktop(&krate, &bundle)? {
+                    bundles.extend(bundle.bundle_paths);
                 }
-
-                cmd_result = StructuredOutput::BundleOutput {
-                    bundles: bundle_paths,
-                };
-            }
-
-            Platform::Web => {
-                tracing::info!("App available at: {}", bundle.build.root_dir().display());
             }
 
+            // Web/ios can just use their root_dir
+            Platform::Web => bundles.push(bundle.build.root_dir()),
             Platform::Ios => {
-                tracing::warn!("Signed iOS bundles are not yet supported");
-                tracing::info!(
-                    "The bundle is available at: {}",
-                    bundle.build.root_dir().display()
-                );
-            }
-
-            Platform::Server => {
-                tracing::info!("Server available at: {}", bundle.build.root_dir().display())
+                tracing::warn!("iOS bundles are not currently codesigned! You will need to codesign the app before distributing.");
+                bundles.push(bundle.build.root_dir())
             }
-            Platform::Liveview => tracing::info!(
-                "Liveview server available at: {}",
-                bundle.build.root_dir().display()
-            ),
+            Platform::Server => bundles.push(bundle.build.root_dir()),
+            Platform::Liveview => bundles.push(bundle.build.root_dir()),
 
+            // todo(jon): we can technically create apks (already do...) just need to expose it
             Platform::Android => {
                 return Err(Error::UnsupportedFeature(
                     "Android bundles are not yet supported".into(),
@@ -127,13 +95,54 @@ impl Bundle {
             }
         };
 
-        Ok(cmd_result)
+        // Copy the bundles to the output directory if one was specified
+        let crate_outdir = bundle.build.krate.crate_out_dir();
+        if let Some(outdir) = self.out_dir.clone().or(crate_outdir) {
+            let outdir = outdir
+                .absolutize()
+                .context("Failed to absolutize output directory")?;
+
+            tracing::info!("Copying bundles to output directory: {}", outdir.display());
+
+            std::fs::create_dir_all(&outdir)?;
+
+            for bundle_path in bundles.iter_mut() {
+                let destination = outdir.join(bundle_path.file_name().unwrap());
+
+                tracing::debug!(
+                    "Copying from {} to {}",
+                    bundle_path.display(),
+                    destination.display()
+                );
+
+                if bundle_path.is_dir() {
+                    dircpy::CopyBuilder::new(&bundle_path, &destination)
+                        .overwrite(true)
+                        .run_par()
+                        .context("Failed to copy the app to output directory")?;
+                } else {
+                    std::fs::copy(&bundle_path, &destination)
+                        .context("Failed to copy the app to output directory")?;
+                }
+
+                *bundle_path = destination;
+            }
+        }
+
+        for bundle_path in bundles.iter() {
+            tracing::info!(
+                "Bundled app at: {}",
+                bundle_path.absolutize().unwrap().display()
+            );
+        }
+
+        Ok(StructuredOutput::BundleOutput { bundles })
     }
 
     fn bundle_desktop(
         &self,
-        krate: DioxusCrate,
-        bundle: AppBundle,
+        krate: &DioxusCrate,
+        bundle: &AppBundle,
     ) -> Result<Vec<tauri_bundler::Bundle>, Error> {
         _ = std::fs::remove_dir_all(krate.bundle_dir(self.build_arguments.platform()));
 

+ 4 - 5
packages/cli/src/config/app.rs

@@ -3,13 +3,12 @@ use std::path::PathBuf;
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub(crate) struct ApplicationConfig {
-    #[serde(default = "asset_dir_default")]
-    pub(crate) asset_dir: PathBuf,
+    #[serde(default)]
+    pub(crate) asset_dir: Option<PathBuf>,
 
     #[serde(default)]
     pub(crate) sub_package: Option<String>,
-}
 
-pub(crate) fn asset_dir_default() -> PathBuf {
-    PathBuf::from("assets")
+    #[serde(default)]
+    pub(crate) out_dir: Option<PathBuf>,
 }

+ 2 - 1
packages/cli/src/config/dioxus_config.rs

@@ -22,8 +22,9 @@ impl Default for DioxusConfig {
     fn default() -> Self {
         Self {
             application: ApplicationConfig {
-                asset_dir: asset_dir_default(),
+                asset_dir: None,
                 sub_package: None,
+                out_dir: None,
             },
             web: WebConfig {
                 app: WebAppConfig {

+ 29 - 8
packages/cli/src/dioxus_crate.rs

@@ -115,17 +115,28 @@ impl DioxusCrate {
         })
     }
 
-    /// 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)
+    /// The asset dir we used to support before manganis became the default.
+    /// This generally was just a folder in your Dioxus.toml called "assets" or "public" where users
+    /// would store their assets.
+    ///
+    /// With manganis you now use `asset!()` and we pick it up automatically.
+    pub(crate) fn legacy_asset_dir(&self) -> Option<PathBuf> {
+        self.config
+            .application
+            .asset_dir
+            .clone()
+            .map(|dir| self.crate_dir().join(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 {
+        let Some(legacy_asset_dir) = self.legacy_asset_dir() else {
+            return files;
+        };
+
+        let Ok(read_dir) = legacy_asset_dir.read_dir() else {
             return files;
         };
 
@@ -136,10 +147,20 @@ impl DioxusCrate {
         files
     }
 
+    /// Get the outdir specified by the Dioxus.toml, relative to the crate directory.
+    /// We don't support workspaces yet since that would cause a collision of bundles per project.
+    pub(crate) fn crate_out_dir(&self) -> Option<PathBuf> {
+        self.config
+            .application
+            .out_dir
+            .as_ref()
+            .map(|out_dir| self.crate_dir().join(out_dir))
+    }
+
     /// 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 {
+    fn internal_out_dir(&self) -> PathBuf {
         let dir = self.workspace_dir().join("target").join("dx");
         std::fs::create_dir_all(&dir).unwrap();
         dir
@@ -153,7 +174,7 @@ impl DioxusCrate {
     /// 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()
+        self.internal_out_dir()
             .join(self.executable_name())
             .join(if release { "release" } else { "debug" })
             .join(platform.build_folder_name())
@@ -164,7 +185,7 @@ impl DioxusCrate {
     /// target/dx/bundle/app/blah.exe
     /// target/dx/bundle/app/public/
     pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf {
-        self.out_dir()
+        self.internal_out_dir()
             .join(self.executable_name())
             .join("bundle")
             .join(platform.build_folder_name())

+ 0 - 62
packages/cli/src/fastfs.rs

@@ -11,68 +11,6 @@ use std::{
 use brotli::enc::BrotliEncoderParams;
 use walkdir::WalkDir;
 
-pub fn copy_asset(src: &Path, dest: &Path) -> std::io::Result<()> {
-    if src.is_dir() {
-        copy_dir_to(src, dest, false)?;
-    } else {
-        std::fs::copy(src, dest)?;
-    }
-
-    Ok(())
-}
-
-pub(crate) fn copy_dir_to(
-    src_dir: &Path,
-    dest_dir: &Path,
-    pre_compress: bool,
-) -> std::io::Result<()> {
-    let entries = std::fs::read_dir(src_dir)?;
-    let mut children: Vec<std::thread::JoinHandle<std::io::Result<()>>> = Vec::new();
-
-    for entry in entries.flatten() {
-        let entry_path = entry.path();
-        let path_relative_to_src = entry_path.strip_prefix(src_dir).unwrap();
-        let output_file_location = dest_dir.join(path_relative_to_src);
-        children.push(std::thread::spawn(move || {
-            if entry.file_type()?.is_dir() {
-                // If the file is a directory, recursively copy it into the output directory
-                if let Err(err) = copy_dir_to(&entry_path, &output_file_location, pre_compress) {
-                    tracing::error!(
-                        "Failed to pre-compress directory {}: {}",
-                        entry_path.display(),
-                        err
-                    );
-                }
-            } else {
-                // Make sure the directory exists
-                std::fs::create_dir_all(output_file_location.parent().unwrap())?;
-                // Copy the file to the output directory
-                std::fs::copy(&entry_path, &output_file_location)?;
-
-                // Then pre-compress the file if needed
-                if pre_compress {
-                    if let Err(err) = pre_compress_file(&output_file_location) {
-                        tracing::error!(
-                            "Failed to pre-compress static assets {}: {}",
-                            output_file_location.display(),
-                            err
-                        );
-                    }
-                    // If pre-compression isn't enabled, we should remove the old compressed file if it exists
-                } else if let Some(compressed_path) = compressed_path(&output_file_location) {
-                    _ = std::fs::remove_file(compressed_path);
-                }
-            }
-
-            Ok(())
-        }));
-    }
-    for child in children {
-        child.join().unwrap()?;
-    }
-    Ok(())
-}
-
 /// Get the path to the compressed version of a file
 fn compressed_path(path: &Path) -> Option<PathBuf> {
     let new_extension = match path.extension() {

+ 9 - 8
packages/cli/src/serve/handle.rs

@@ -172,14 +172,15 @@ impl AppHandle {
         tracing::debug!("Hotreloading asset {changed_file:?} in target {asset_dir:?}");
 
         // If the asset shares the same name in the bundle, reload that
-        let legacy_asset_dir = self.app.build.krate.legacy_asset_dir();
-        if changed_file.starts_with(&legacy_asset_dir) {
-            tracing::debug!("Hotreloading legacy asset {changed_file:?}");
-            let trimmed = changed_file.strip_prefix(legacy_asset_dir).unwrap();
-            let res = std::fs::copy(changed_file, asset_dir.join(trimmed));
-            bundled_name = Some(trimmed.to_path_buf());
-            if let Err(e) = res {
-                tracing::debug!("Failed to hotreload legacy asset {e}");
+        if let Some(legacy_asset_dir) = self.app.build.krate.legacy_asset_dir() {
+            if changed_file.starts_with(&legacy_asset_dir) {
+                tracing::debug!("Hotreloading legacy asset {changed_file:?}");
+                let trimmed = changed_file.strip_prefix(legacy_asset_dir).unwrap();
+                let res = std::fs::copy(changed_file, asset_dir.join(trimmed));
+                bundled_name = Some(trimmed.to_path_buf());
+                if let Err(e) = res {
+                    tracing::debug!("Failed to hotreload legacy asset {e}");
+                }
             }
         }