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>
This commit is contained in:
authored and GitHub committed 2025-01-16 15:17:33 -08:00
1 parent db16de15c5
commit 19d5fae12d
9 files changed
+152 -143

No files matched your search

+1
View File
@@ -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
View File
@@ -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
View File
@@ -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()
);
tracing::warn!("iOS bundles are not currently codesigned! You will need to codesign the app before distributing.");
bundles.push(bundle.build.root_dir())
}
Platform::Server => bundles.push(bundle.build.root_dir()),
Platform::Liveview => bundles.push(bundle.build.root_dir()),
Platform::Server => {
tracing::info!("Server available at: {}", bundle.build.root_dir().display())
}
Platform::Liveview => tracing::info!(
"Liveview server available at: {}",
bundle.build.root_dir().display()
),
// 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
View File
@@ -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
View File
@@ -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
View File
@@ -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())
-62
View File
@@ -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
View File
@@ -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}");
}
}
}