cli json output, dx bundle fix, dx serve --platform android, race condition, drop ssg platform (#3186)
* clean up logging to avoid random extra trace * fix race condition with updates * properly wire up verbose * fix bundling (macos) and logging * Add structured output * clean up clap names * extract out wire format * switch structured key to json * fix random println, fallback to `dioxus/platform` * clean up logging for run/build * clean up logging around project * remove manual exits * fix tokio runtime for mobile * rework dog app * rip out ssg * Switch dioxus/axum to dioxus/server * add android template inline * pre restructure for bundle prep * add the whole res directory * Better theme for the app * remove mobile demo now that most apps work natively * self-referential android * only use deep linking for assets * fix imports for android * clippy, fixup ios and android * I'm not boxing compiler message you can't make me * fix clippy on unix
This commit is contained in:
1 parent
8a998e7d49
commit
ac3e33af46
116 files changed
+2367
-6091
No files matched your search
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, Platform, ProgressRx,
|
||||
ProgressTx, Result,
|
||||
AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, ProgressRx,
|
||||
ProgressTx, Result, StructuredOutput,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -40,6 +40,7 @@ impl Builder {
|
||||
pub(crate) fn start(krate: &DioxusCrate, args: BuildArgs) -> Result<Self> {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
let request = BuildRequest::new(krate.clone(), args, tx.clone());
|
||||
|
||||
Ok(Self {
|
||||
krate: krate.clone(),
|
||||
request: request.clone(),
|
||||
@@ -49,14 +50,7 @@ impl Builder {
|
||||
// We wont bother verifying on subsequent builds
|
||||
request.verify_tooling().await?;
|
||||
|
||||
let res = request.build_all().await;
|
||||
|
||||
// The first launch gets some extra logging :)
|
||||
if res.is_ok() {
|
||||
tracing::info!("Build completed successfully, launching app! 💫")
|
||||
}
|
||||
|
||||
res
|
||||
request.build_all().await
|
||||
}),
|
||||
tx,
|
||||
rx,
|
||||
@@ -91,79 +85,76 @@ impl Builder {
|
||||
},
|
||||
};
|
||||
|
||||
tracing::trace!("Build update: {update:?}");
|
||||
|
||||
// Update the internal stage of the build so the UI can render it
|
||||
match &update {
|
||||
BuildUpdate::Progress { stage } => {
|
||||
// Prevent updates from flowing in after the build has already finished
|
||||
if !self.is_finished() {
|
||||
self.stage = stage.clone();
|
||||
}
|
||||
|
||||
match stage {
|
||||
BuildStage::Initializing => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
BuildStage::Starting {
|
||||
crate_count,
|
||||
platform,
|
||||
} => {
|
||||
if *platform == Platform::Server {
|
||||
self.expected_crates_server = *crate_count;
|
||||
} else {
|
||||
self.expected_crates = *crate_count;
|
||||
match stage {
|
||||
BuildStage::Initializing => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
}
|
||||
BuildStage::InstallingTooling {} => {}
|
||||
BuildStage::Compiling {
|
||||
current,
|
||||
total,
|
||||
platform,
|
||||
..
|
||||
} => {
|
||||
if *platform == Platform::Server {
|
||||
self.compiled_crates_server = *current;
|
||||
self.expected_crates_server = *total;
|
||||
} else {
|
||||
self.compiled_crates = *current;
|
||||
self.expected_crates = *total;
|
||||
BuildStage::Starting {
|
||||
crate_count,
|
||||
is_server,
|
||||
} => {
|
||||
if *is_server {
|
||||
self.expected_crates_server = *crate_count;
|
||||
} else {
|
||||
self.expected_crates = *crate_count;
|
||||
}
|
||||
}
|
||||
BuildStage::InstallingTooling {} => {}
|
||||
BuildStage::Compiling {
|
||||
current,
|
||||
total,
|
||||
is_server,
|
||||
..
|
||||
} => {
|
||||
if *is_server {
|
||||
self.compiled_crates_server = *current;
|
||||
self.expected_crates_server = *total;
|
||||
} else {
|
||||
self.compiled_crates = *current;
|
||||
self.expected_crates = *total;
|
||||
}
|
||||
|
||||
if self.compile_start.is_none() {
|
||||
self.compile_start = Some(Instant::now());
|
||||
if self.compile_start.is_none() {
|
||||
self.compile_start = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
BuildStage::Bundling {} => {
|
||||
self.complete_compile();
|
||||
self.bundling_progress = 0.0;
|
||||
self.bundle_start = Some(Instant::now());
|
||||
}
|
||||
BuildStage::OptimizingWasm {} => {}
|
||||
BuildStage::CopyingAssets { current, total, .. } => {
|
||||
self.bundling_progress = *current as f64 / *total as f64;
|
||||
}
|
||||
BuildStage::Success => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Failed => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Aborted => {}
|
||||
BuildStage::Restarting => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.expected_crates = 1;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
BuildStage::RunningBindgen {} => {
|
||||
self.bundling_progress = 0.5;
|
||||
BuildStage::Bundling {} => {
|
||||
self.complete_compile();
|
||||
self.bundling_progress = 0.0;
|
||||
self.bundle_start = Some(Instant::now());
|
||||
}
|
||||
BuildStage::OptimizingWasm {} => {}
|
||||
BuildStage::CopyingAssets { current, total, .. } => {
|
||||
self.bundling_progress = *current as f64 / *total as f64;
|
||||
}
|
||||
BuildStage::Success => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Failed => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Aborted => {}
|
||||
BuildStage::Restarting => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.expected_crates = 1;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
BuildStage::RunningBindgen {} => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,10 +215,43 @@ impl Builder {
|
||||
pub(crate) async fn finish(&mut self) -> Result<AppBundle> {
|
||||
loop {
|
||||
match self.wait().await {
|
||||
BuildUpdate::BuildReady { bundle } => return Ok(bundle),
|
||||
BuildUpdate::BuildFailed { err } => return Err(err),
|
||||
BuildUpdate::Progress { .. } => {}
|
||||
BuildUpdate::CompilerMessage { .. } => {}
|
||||
BuildUpdate::Progress { stage } => {
|
||||
match &stage {
|
||||
BuildStage::Compiling {
|
||||
current,
|
||||
total,
|
||||
krate,
|
||||
..
|
||||
} => {
|
||||
tracing::info!("Compiling [{current:>3}/{total}]: {krate}");
|
||||
}
|
||||
BuildStage::RunningBindgen => tracing::info!("Running wasm-bindgen..."),
|
||||
BuildStage::CopyingAssets {
|
||||
current,
|
||||
total,
|
||||
path,
|
||||
} => {
|
||||
tracing::info!("Copying asset ({current}/{total}): {}", path.display());
|
||||
}
|
||||
BuildStage::Bundling => tracing::info!("Bundling app..."),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tracing::info!(json = ?StructuredOutput::BuildUpdate { stage: stage.clone() });
|
||||
}
|
||||
BuildUpdate::CompilerMessage { message } => {
|
||||
tracing::info!(json = ?StructuredOutput::CargoOutput { message: message.clone() }, %message);
|
||||
}
|
||||
BuildUpdate::BuildReady { bundle } => {
|
||||
tracing::debug!(json = ?StructuredOutput::BuildFinished {
|
||||
path: bundle.build.root_dir(),
|
||||
});
|
||||
return Ok(bundle);
|
||||
}
|
||||
BuildUpdate::BuildFailed { err } => {
|
||||
tracing::error!(?err, json = ?StructuredOutput::Error { message: err.to_string() });
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,7 +299,7 @@ impl Builder {
|
||||
self.bundling_progress
|
||||
}
|
||||
|
||||
fn is_finished(&self) -> bool {
|
||||
pub(crate) fn is_finished(&self) -> bool {
|
||||
match self.stage {
|
||||
BuildStage::Success => true,
|
||||
BuildStage::Failed => true,
|
||||
@@ -3,13 +3,16 @@ use crate::{assets::AssetManifest, TraceSrc};
|
||||
use crate::{BuildRequest, Platform};
|
||||
use anyhow::Context;
|
||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::{
|
||||
fs::create_dir_all,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{sync::atomic::AtomicUsize, time::Duration};
|
||||
use tokio::process::Command;
|
||||
use wasm_bindgen_cli_support::Bindgen;
|
||||
|
||||
use super::templates::InfoPlistData;
|
||||
|
||||
/// The end result of a build.
|
||||
///
|
||||
/// Contains the final asset manifest, the executables, and the workdir.
|
||||
@@ -93,13 +96,14 @@ pub(crate) struct AppBundle {
|
||||
pub struct BuildArtifacts {
|
||||
pub(crate) exe: PathBuf,
|
||||
pub(crate) assets: AssetManifest,
|
||||
pub(crate) time_taken: Duration,
|
||||
}
|
||||
|
||||
impl AppBundle {
|
||||
/// ## Web:
|
||||
/// Create a folder that is somewhat similar to an app-image (exe + asset)
|
||||
/// The server is dropped into the `web` folder, even if there's no `public` folder.
|
||||
/// If there's no server (SPA/static-gen), we still use the `web` folder, but it only contains the
|
||||
/// If there's no server (SPA), we still use the `web` folder, but it only contains the
|
||||
/// public folder.
|
||||
/// ```
|
||||
/// web/
|
||||
@@ -255,52 +259,40 @@ impl AppBundle {
|
||||
/// root().join(bundled)
|
||||
/// ```
|
||||
pub(crate) async fn new(
|
||||
request: BuildRequest,
|
||||
build: BuildRequest,
|
||||
app: BuildArtifacts,
|
||||
server: Option<BuildArtifacts>,
|
||||
) -> Result<Self> {
|
||||
let bundle = Self {
|
||||
app,
|
||||
server,
|
||||
build: request,
|
||||
};
|
||||
let bundle = Self { app, server, build };
|
||||
|
||||
tracing::debug!("Assembling app bundle");
|
||||
|
||||
bundle.build.status_start_bundle();
|
||||
bundle.prepare_build_dir()?;
|
||||
bundle.write_main_executable().await?;
|
||||
/*
|
||||
assume the build dir is already created by BuildRequest
|
||||
todo(jon): maybe refactor this a bit to force AppBundle to be created before it can be filled in
|
||||
*/
|
||||
bundle
|
||||
.write_main_executable()
|
||||
.await
|
||||
.context("Failed to write main executable")?;
|
||||
bundle.write_server_executable().await?;
|
||||
bundle.write_assets().await?;
|
||||
bundle
|
||||
.write_assets()
|
||||
.await
|
||||
.context("Failed to write assets")?;
|
||||
bundle.write_metadata().await?;
|
||||
bundle.optimize().await?;
|
||||
bundle
|
||||
.assemble()
|
||||
.await
|
||||
.context("Failed to assemble app bundle")?;
|
||||
|
||||
tracing::debug!("Bundle created at {}", bundle.build.root_dir().display());
|
||||
|
||||
Ok(bundle)
|
||||
}
|
||||
|
||||
/// We only really currently care about:
|
||||
///
|
||||
/// - app dir (.app, .exe, .apk, etc)
|
||||
/// - assets dir
|
||||
/// - exe dir (.exe, .app, .apk, etc)
|
||||
/// - extra scaffolding
|
||||
///
|
||||
/// It's not guaranteed that they're different from any other folder
|
||||
fn prepare_build_dir(&self) -> Result<()> {
|
||||
_ = std::fs::remove_dir_all(self.app_dir());
|
||||
|
||||
create_dir_all(self.app_dir())?;
|
||||
create_dir_all(self.exe_dir())?;
|
||||
create_dir_all(self.asset_dir())?;
|
||||
|
||||
// we could download the templates from somewhere (github?) but after having banged my head against
|
||||
// cargo-mobile2 for ages, I give up with that. We're literally just going to hardcode the templates
|
||||
// by writing them here.
|
||||
if let Platform::Android = self.build.build.platform() {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Take the output of rustc and make it into the main exe of the bundle
|
||||
///
|
||||
/// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations
|
||||
@@ -333,18 +325,17 @@ impl AppBundle {
|
||||
Platform::Web => {
|
||||
// Run wasm-bindgen and drop its output into the assets folder under "dioxus"
|
||||
self.build.status_wasm_bindgen_start();
|
||||
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.exe_dir())
|
||||
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.build.exe_dir())
|
||||
.await?;
|
||||
|
||||
// Only run wasm-opt if the feature is enabled
|
||||
// Wasm-opt has an expensive build script that makes it annoying to keep enabled for iterative dev
|
||||
// We put it behind the "wasm-opt" feature flag so that it can be disabled when iterating on the cli
|
||||
self.build.status_wasm_opt_start();
|
||||
self.run_wasm_opt(&self.exe_dir())?;
|
||||
self.run_wasm_opt(&self.build.exe_dir())?;
|
||||
|
||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||
std::fs::write(
|
||||
self.app_dir().join("index.html"),
|
||||
self.build.root_dir().join("index.html"),
|
||||
self.build.prepare_html()?,
|
||||
)?;
|
||||
}
|
||||
@@ -385,7 +376,7 @@ impl AppBundle {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let asset_dir = self.asset_dir();
|
||||
let asset_dir = self.build.asset_dir();
|
||||
|
||||
// First, clear the asset dir
|
||||
// todo(jon): cache the asset dir, removing old files and only copying new ones that changed since the last build
|
||||
@@ -418,10 +409,9 @@ impl AppBundle {
|
||||
// Parallel Copy over the assets and keep track of progress with an atomic counter
|
||||
// todo: we want to use the fastfs variant that knows how to parallelize folders, too
|
||||
assets_to_transfer.par_iter().try_for_each(|(from, to)| {
|
||||
self.build.status_copying_asset(
|
||||
assets_finished.fetch_add(0, std::sync::atomic::Ordering::SeqCst),
|
||||
asset_count,
|
||||
from.clone(),
|
||||
tracing::trace!(
|
||||
"Starting asset copy {current}/{asset_count} from {from:?}",
|
||||
current = assets_finished.fetch_add(0, std::sync::atomic::Ordering::SeqCst),
|
||||
);
|
||||
|
||||
// todo(jon): implement optimize + pre_compress on the asset type
|
||||
@@ -431,7 +421,7 @@ impl AppBundle {
|
||||
tracing::error!("Failed to copy asset {from:?}: {err}");
|
||||
}
|
||||
|
||||
self.build.status_copying_asset(
|
||||
self.build.status_copied_asset(
|
||||
assets_finished.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1,
|
||||
asset_count,
|
||||
from.clone(),
|
||||
@@ -443,66 +433,11 @@ impl AppBundle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The directory in which we'll put the main exe
|
||||
///
|
||||
/// Mac, Android, Web are a little weird
|
||||
/// - mac wants to be in Contents/MacOS
|
||||
/// - android wants to be in jniLibs/arm64-v8a (or others, depending on the platform / architecture)
|
||||
/// - web wants to be in wasm (which... we don't really need to, we could just drop the wasm into public and it would work)
|
||||
///
|
||||
/// I think all others are just in the root folder
|
||||
///
|
||||
/// todo(jon): investigate if we need to put .wasm in `wasm`. It kinda leaks implementation details, which ideally we don't want to do.
|
||||
pub fn exe_dir(&self) -> PathBuf {
|
||||
match self.build.build.platform() {
|
||||
Platform::MacOS => self.app_dir().join("Contents").join("MacOS"),
|
||||
Platform::Android => self.app_dir().join("jniLibs").join("arm64-v8a"),
|
||||
Platform::Web => self.app_dir().join("wasm"),
|
||||
|
||||
// these are all the same, I think?
|
||||
Platform::Windows
|
||||
| Platform::Linux
|
||||
| Platform::Ios
|
||||
| Platform::Server
|
||||
| Platform::Liveview => self.app_dir(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The item that we'll try to run directly if we need to.
|
||||
///
|
||||
/// todo(jon): we should name the app properly instead of making up the exe name. It's kinda okay for dev mode, but def not okay for prod
|
||||
pub fn main_exe(&self) -> PathBuf {
|
||||
// todo(jon): this could just be named `App` or the name of the app like `Raycast` in `Raycast.app`
|
||||
match self.build.build.platform() {
|
||||
Platform::MacOS => self.exe_dir().join("DioxusApp"),
|
||||
Platform::Ios => self.exe_dir().join("DioxusApp"),
|
||||
Platform::Server => self.exe_dir().join("server"),
|
||||
Platform::Liveview => self.exe_dir().join("server"),
|
||||
Platform::Windows => self.exe_dir().join("app.exe"),
|
||||
Platform::Linux => self.exe_dir().join("AppRun"), // from the appimage spec, the root exe needs to be named `AppRun`
|
||||
Platform::Android => self.exe_dir().join("libdioxusapp.so"), // from the apk spec, the root exe will actually be a shared library
|
||||
Platform::Web => unimplemented!("there's no main exe on web"), // this will be wrong, I think, but not important?
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asset_dir(&self) -> PathBuf {
|
||||
match self.build.build.platform() {
|
||||
// macos why are you weird
|
||||
Platform::MacOS => self
|
||||
.app_dir()
|
||||
.join("Contents")
|
||||
.join("Resources")
|
||||
.join("assets"),
|
||||
|
||||
// everyone else is soooo normal, just app/assets :)
|
||||
Platform::Web
|
||||
| Platform::Ios
|
||||
| Platform::Windows
|
||||
| Platform::Linux
|
||||
| Platform::Android
|
||||
| Platform::Server
|
||||
| Platform::Liveview => self.app_dir().join("assets"),
|
||||
}
|
||||
self.build.exe_dir().join(self.build.platform_exe_name())
|
||||
}
|
||||
|
||||
/// We always put the server in the `web` folder!
|
||||
@@ -531,15 +466,15 @@ impl AppBundle {
|
||||
// write the Info.plist file
|
||||
match self.build.build.platform() {
|
||||
Platform::MacOS => {
|
||||
let src = include_str!("../../assets/macos/mac.plist");
|
||||
let dest = self.app_dir().join("Contents").join("Info.plist");
|
||||
std::fs::write(dest, src)?;
|
||||
let dest = self.build.root_dir().join("Contents").join("Info.plist");
|
||||
let plist = self.macos_plist_contents()?;
|
||||
std::fs::write(dest, plist)?;
|
||||
}
|
||||
|
||||
Platform::Ios => {
|
||||
let src = include_str!("../../assets/ios/ios.plist");
|
||||
let dest = self.app_dir().join("Info.plist");
|
||||
std::fs::write(dest, src)?;
|
||||
let dest = self.build.root_dir().join("Info.plist");
|
||||
let plist = self.ios_plist_contents()?;
|
||||
std::fs::write(dest, plist)?;
|
||||
}
|
||||
|
||||
// AndroidManifest.xml
|
||||
@@ -575,7 +510,7 @@ impl AppBundle {
|
||||
.krate
|
||||
.should_pre_compress_web_assets(self.build.build.release);
|
||||
|
||||
let bindgen_dir = self.exe_dir();
|
||||
let bindgen_dir = self.build.exe_dir();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
crate::fastfs::pre_compress_folder(&bindgen_dir, pre_compress)
|
||||
})
|
||||
@@ -618,37 +553,6 @@ impl AppBundle {
|
||||
None
|
||||
}
|
||||
|
||||
/// returns the path to .app/.apk/.appimage folder
|
||||
///
|
||||
/// we only add an extension to the folders where it sorta matters that it's named with the extension.
|
||||
/// for example, on mac, the `.app` indicates we can `open` it and it pulls in icons, dylibs, etc.
|
||||
///
|
||||
/// for our simulator-based platforms, this is less important since they need to be zipped up anyways
|
||||
/// to run in the simulator.
|
||||
///
|
||||
/// For windows/linux, it's also not important since we're just running the exe directly out of the folder
|
||||
pub(crate) fn app_dir(&self) -> PathBuf {
|
||||
let platform_dir = self
|
||||
.build
|
||||
.krate
|
||||
.build_dir(self.build.build.platform(), self.build.build.release);
|
||||
|
||||
match self.build.build.platform() {
|
||||
Platform::Web => platform_dir.join("public"),
|
||||
Platform::Server => platform_dir.clone(), // ends up *next* to the public folder
|
||||
|
||||
// These might not actually need to be called `.app` but it does let us run these with `open`
|
||||
Platform::MacOS => platform_dir.join("DioxusApp.app"),
|
||||
Platform::Ios => platform_dir.join("DioxusApp.app"),
|
||||
|
||||
// in theory, these all could end up in the build dir
|
||||
Platform::Linux => platform_dir.join("app"), // .appimage (after bundling)
|
||||
Platform::Windows => platform_dir.join("app"), // .exe (after bundling)
|
||||
Platform::Android => platform_dir.join("app"), // .apk (after bundling)
|
||||
Platform::Liveview => platform_dir.join("app"), // .exe (after bundling)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run_wasm_bindgen(
|
||||
&self,
|
||||
input_path: &Path,
|
||||
@@ -691,7 +595,6 @@ impl AppBundle {
|
||||
if !self.build.build.release {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.build.status_optimizing_wasm();
|
||||
|
||||
#[cfg(feature = "optimizations")]
|
||||
@@ -799,4 +702,76 @@ impl AppBundle {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn bundle_identifier(&self) -> String {
|
||||
format!("com.dioxuslabs.{}", self.build.krate.executable_name())
|
||||
}
|
||||
|
||||
fn macos_plist_contents(&self) -> Result<String> {
|
||||
handlebars::Handlebars::new()
|
||||
.render_template(
|
||||
include_str!("../../assets/macos/mac.plist.hbs"),
|
||||
&InfoPlistData {
|
||||
display_name: self.build.platform_exe_name(),
|
||||
bundle_name: self.build.platform_exe_name(),
|
||||
executable_name: self.build.platform_exe_name(),
|
||||
bundle_identifier: format!("com.dioxuslabs.{}", self.build.platform_exe_name()),
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn ios_plist_contents(&self) -> Result<String> {
|
||||
handlebars::Handlebars::new()
|
||||
.render_template(
|
||||
include_str!("../../assets/ios/ios.plist.hbs"),
|
||||
&InfoPlistData {
|
||||
display_name: self.build.platform_exe_name(),
|
||||
bundle_name: self.build.platform_exe_name(),
|
||||
executable_name: self.build.platform_exe_name(),
|
||||
bundle_identifier: format!("com.dioxuslabs.{}", self.build.platform_exe_name()),
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Run any final tools to produce apks or other artifacts we might need.
|
||||
async fn assemble(&self) -> Result<()> {
|
||||
if let Platform::Android = self.build.build.platform() {
|
||||
// make sure we can execute the gradlew script
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
std::fs::set_permissions(
|
||||
self.build.root_dir().join("gradlew"),
|
||||
std::fs::Permissions::from_mode(0o755),
|
||||
)?;
|
||||
}
|
||||
|
||||
let output = Command::new("./gradlew")
|
||||
.arg("assembleDebug")
|
||||
.current_dir(self.build.root_dir())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to assemble apk: {output:?}").into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn apk_path(&self) -> PathBuf {
|
||||
self.build
|
||||
.root_dir()
|
||||
.join("app")
|
||||
.join("build")
|
||||
.join("outputs")
|
||||
.join("apk")
|
||||
.join("debug")
|
||||
.join("app-debug.apk")
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,15 @@
|
||||
//! Uses a request -> response architecture that allows you to monitor the progress with an optional message
|
||||
//! receiver.
|
||||
|
||||
mod build;
|
||||
mod builder;
|
||||
mod bundle;
|
||||
mod progress;
|
||||
mod runner;
|
||||
mod request;
|
||||
mod templates;
|
||||
mod verify;
|
||||
mod web;
|
||||
|
||||
pub(crate) use build::*;
|
||||
pub(crate) use builder::*;
|
||||
pub(crate) use bundle::*;
|
||||
pub(crate) use progress::*;
|
||||
pub(crate) use runner::*;
|
||||
pub(crate) use request::*;
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Report progress about the build to the user. We use channels to report progress back to the CLI.
|
||||
use crate::{AppBundle, BuildRequest, Platform, TraceSrc};
|
||||
use crate::{AppBundle, BuildRequest, BuildStage, Platform, TraceSrc};
|
||||
use cargo_metadata::CompilerMessage;
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use std::path::PathBuf;
|
||||
@@ -16,46 +16,12 @@ pub(crate) enum BuildUpdate {
|
||||
BuildFailed { err: crate::Error },
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum BuildStage {
|
||||
Initializing,
|
||||
Starting {
|
||||
platform: Platform,
|
||||
crate_count: usize,
|
||||
},
|
||||
InstallingTooling {},
|
||||
Compiling {
|
||||
platform: Platform,
|
||||
current: usize,
|
||||
total: usize,
|
||||
krate: String,
|
||||
},
|
||||
Bundling {},
|
||||
RunningBindgen {},
|
||||
OptimizingWasm {},
|
||||
CopyingAssets {
|
||||
current: usize,
|
||||
total: usize,
|
||||
path: PathBuf,
|
||||
},
|
||||
Success,
|
||||
Failed,
|
||||
Aborted,
|
||||
Restarting,
|
||||
}
|
||||
|
||||
impl BuildRequest {
|
||||
pub(crate) fn status_wasm_bindgen_start(&self) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::RunningBindgen {},
|
||||
});
|
||||
}
|
||||
pub(crate) fn status_wasm_opt_start(&self) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::RunningBindgen {},
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn status_start_bundle(&self) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
@@ -73,19 +39,13 @@ impl BuildRequest {
|
||||
tracing::trace!(dx_src = ?TraceSrc::Cargo, "{line}");
|
||||
}
|
||||
|
||||
pub(crate) fn status_build_progress(
|
||||
&self,
|
||||
count: usize,
|
||||
total: usize,
|
||||
name: String,
|
||||
platform: Platform,
|
||||
) {
|
||||
pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::Compiling {
|
||||
current: count,
|
||||
total,
|
||||
krate: name,
|
||||
platform,
|
||||
is_server: self.is_server(),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -93,14 +53,13 @@ impl BuildRequest {
|
||||
pub(crate) fn status_starting_build(&self, crate_count: usize) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::Starting {
|
||||
platform: self.build.platform(),
|
||||
is_server: self.build.platform() == Platform::Server,
|
||||
crate_count,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn status_copying_asset(&self, current: usize, total: usize, path: PathBuf) {
|
||||
tracing::trace!("Status copying asset {current}/{total} from {path:?}");
|
||||
pub(crate) fn status_copied_asset(&self, current: usize, total: usize, path: PathBuf) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::CopyingAssets {
|
||||
current,
|
||||
@@ -121,4 +80,8 @@ impl BuildRequest {
|
||||
stage: BuildStage::InstallingTooling {},
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn is_server(&self) -> bool {
|
||||
self.build.platform() == Platform::Server
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,11 @@ use super::{progress::ProgressTx, BuildArtifacts};
|
||||
use crate::dioxus_crate::DioxusCrate;
|
||||
use crate::Result;
|
||||
use crate::{assets::AssetManifest, TraceSrc};
|
||||
use crate::{build::BuildArgs, link::LinkAction};
|
||||
use crate::{link::LinkAction, BuildArgs};
|
||||
use crate::{AppBundle, Platform};
|
||||
use anyhow::Context;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
};
|
||||
use std::{path::PathBuf, process::Stdio, time::Instant};
|
||||
use tokio::{io::AsyncBufReadExt, process::Command};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -44,17 +41,32 @@ impl BuildRequest {
|
||||
pub(crate) async fn build_all(self) -> Result<AppBundle> {
|
||||
tracing::debug!("Running build command...");
|
||||
|
||||
let (app, server) =
|
||||
futures_util::future::try_join(self.build_app(), self.build_server()).await?;
|
||||
let (app, server) = self.build_concurrent().await?;
|
||||
|
||||
AppBundle::new(self, app, server).await
|
||||
}
|
||||
|
||||
/// Run the build command with a pretty loader, returning the executable output location
|
||||
async fn build_concurrent(&self) -> Result<(BuildArtifacts, Option<BuildArtifacts>)> {
|
||||
let (app, server) =
|
||||
futures_util::future::try_join(self.build_app(), self.build_server()).await?;
|
||||
|
||||
Ok((app, server))
|
||||
}
|
||||
|
||||
pub(crate) async fn build_app(&self) -> Result<BuildArtifacts> {
|
||||
tracing::debug!("Building app...");
|
||||
|
||||
let start = Instant::now();
|
||||
self.prepare_build_dir()?;
|
||||
let exe = self.build_cargo().await?;
|
||||
let assets = self.collect_assets(&exe).await?;
|
||||
Ok(BuildArtifacts { exe, assets })
|
||||
let assets = self.collect_assets().await?;
|
||||
|
||||
Ok(BuildArtifacts {
|
||||
exe,
|
||||
assets,
|
||||
time_taken: start.elapsed(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn build_server(&self) -> Result<Option<BuildArtifacts>> {
|
||||
@@ -87,8 +99,9 @@ impl BuildRequest {
|
||||
.current_dir(self.krate.crate_dir())
|
||||
.arg("--message-format")
|
||||
.arg("json-diagnostic-rendered-ansi")
|
||||
.args(self.build_arguments());
|
||||
// .env("RUSTFLAGS", self.rust_flags());
|
||||
.args(self.build_arguments())
|
||||
.envs(self.env_vars());
|
||||
// .args(["--", "-Csave-temps=y"]);
|
||||
|
||||
if let Some(target_dir) = self.custom_target_dir.as_ref() {
|
||||
cmd.env("CARGO_TARGET_DIR", target_dir);
|
||||
@@ -153,7 +166,6 @@ impl BuildRequest {
|
||||
units_compiled,
|
||||
crate_count,
|
||||
artifact.target.name,
|
||||
self.build.platform(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -188,19 +200,14 @@ impl BuildRequest {
|
||||
/// This will execute `dx` with an env var set to force `dx` to operate as a linker, and then
|
||||
/// traverse the .o and .rlib files rustc passes that new `dx` instance, collecting the link
|
||||
/// tables marked by manganis and parsing them as a ResourceAsset.
|
||||
pub(crate) async fn collect_assets(&self, exe: &Path) -> Result<AssetManifest> {
|
||||
pub(crate) async fn collect_assets(&self) -> Result<AssetManifest> {
|
||||
tracing::debug!("Collecting assets ...");
|
||||
|
||||
// If assets are skipped, we don't need to collect them
|
||||
if self.build.skip_assets {
|
||||
return Ok(AssetManifest::default());
|
||||
}
|
||||
|
||||
let mut manifest = AssetManifest::default();
|
||||
|
||||
_ = manifest.add_from_object_path(exe.to_path_buf());
|
||||
|
||||
Ok(manifest)
|
||||
self.deep_linker_asset_extract().await
|
||||
}
|
||||
|
||||
/// Create a list of arguments for cargo builds
|
||||
@@ -254,11 +261,8 @@ impl BuildRequest {
|
||||
}
|
||||
}
|
||||
|
||||
if self.build.verbose {
|
||||
cargo_args.push("--verbose".to_string());
|
||||
} else {
|
||||
cargo_args.push("--quiet".to_string());
|
||||
}
|
||||
// We always run in verbose since the CLI itself is the one doing the presentation
|
||||
cargo_args.push("--verbose".to_string());
|
||||
|
||||
if self.build.target_args.no_default_features {
|
||||
cargo_args.push("--no-default-features".to_string());
|
||||
@@ -293,7 +297,7 @@ impl BuildRequest {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn rust_flags(&self) -> String {
|
||||
pub(crate) fn android_rust_flags(&self) -> String {
|
||||
let mut rust_flags = std::env::var("RUSTFLAGS").unwrap_or_default();
|
||||
|
||||
if self.build.platform() == Platform::Android {
|
||||
@@ -355,6 +359,7 @@ impl BuildRequest {
|
||||
.arg("-Z")
|
||||
.arg("unstable-options")
|
||||
.args(self.build_arguments())
|
||||
.envs(self.env_vars())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
@@ -394,7 +399,6 @@ impl BuildRequest {
|
||||
///
|
||||
/// There's a chance that's not actually true, so this function is kept around in case we do
|
||||
/// need to revert to "deep extraction".
|
||||
#[allow(unused)]
|
||||
async fn deep_linker_asset_extract(&self) -> Result<AssetManifest> {
|
||||
// Create a temp file to put the output of the args
|
||||
// We need to do this since rustc won't actually print the link args to stdout, so we need to
|
||||
@@ -406,9 +410,9 @@ impl BuildRequest {
|
||||
//
|
||||
// This will force `dx` to look through the incremental cache and find the assets from the previous build
|
||||
Command::new("cargo")
|
||||
// .env("RUSTFLAGS", self.rust_flags())
|
||||
.arg("rustc")
|
||||
.args(self.build_arguments())
|
||||
.envs(self.env_vars())
|
||||
.arg("--offline") /* don't use the network, should already be resolved */
|
||||
.arg("--")
|
||||
.arg(format!(
|
||||
@@ -422,7 +426,7 @@ impl BuildRequest {
|
||||
.env(
|
||||
LinkAction::ENV_VAR_NAME,
|
||||
LinkAction::BuildAssetManifest {
|
||||
destination: tmp_file.path().to_path_buf(),
|
||||
destination: tmp_file.path().to_path_buf().clone(),
|
||||
}
|
||||
.to_json(),
|
||||
)
|
||||
@@ -432,6 +436,351 @@ impl BuildRequest {
|
||||
.await?;
|
||||
|
||||
// The linker wrote the manifest to the temp file, let's load it!
|
||||
Ok(AssetManifest::load_from_file(tmp_file.path())?)
|
||||
let manifest = AssetManifest::load_from_file(tmp_file.path())?;
|
||||
|
||||
if let Ok(path) = std::env::var("DEEPLINK").map(|s| s.parse::<PathBuf>().unwrap()) {
|
||||
_ = tmp_file.persist(path);
|
||||
}
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
fn env_vars(&self) -> Vec<(&str, String)> {
|
||||
let mut env_vars = vec![];
|
||||
|
||||
if self.build.platform() == Platform::Android {
|
||||
let app = self.root_dir().join("app");
|
||||
let app_main = app.join("src").join("main");
|
||||
let app_kotlin = app_main.join("kotlin");
|
||||
let app_kotlin_out = app_kotlin.join("com").join("example").join("androidfinal");
|
||||
|
||||
env_vars.push((
|
||||
"WRY_ANDROID_PACKAGE",
|
||||
"com.example.androidfinal".to_string(),
|
||||
));
|
||||
env_vars.push(("WRY_ANDROID_LIBRARY", "androidfinal".to_string()));
|
||||
env_vars.push((
|
||||
"WRY_ANDROID_KOTLIN_FILES_OUT_DIR",
|
||||
app_kotlin_out.display().to_string(),
|
||||
));
|
||||
|
||||
env_vars.push(("RUSTFLAGS", self.android_rust_flags()))
|
||||
};
|
||||
|
||||
env_vars
|
||||
}
|
||||
|
||||
/// We only really currently care about:
|
||||
///
|
||||
/// - app dir (.app, .exe, .apk, etc)
|
||||
/// - assets dir
|
||||
/// - exe dir (.exe, .app, .apk, etc)
|
||||
/// - extra scaffolding
|
||||
///
|
||||
/// It's not guaranteed that they're different from any other folder
|
||||
fn prepare_build_dir(&self) -> Result<()> {
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::fs::{create_dir_all, remove_dir_all};
|
||||
|
||||
static INITIALIZED: OnceCell<Result<()>> = OnceCell::new();
|
||||
|
||||
let success = INITIALIZED.get_or_init(|| {
|
||||
_ = remove_dir_all(self.root_dir());
|
||||
|
||||
create_dir_all(self.root_dir())?;
|
||||
create_dir_all(self.exe_dir())?;
|
||||
create_dir_all(self.asset_dir())?;
|
||||
|
||||
tracing::debug!("Initialized Root dir: {:?}", self.root_dir());
|
||||
tracing::debug!("Initialized Exe dir: {:?}", self.exe_dir());
|
||||
tracing::debug!("Initialized Asset dir: {:?}", self.asset_dir());
|
||||
|
||||
// we could download the templates from somewhere (github?) but after having banged my head against
|
||||
// cargo-mobile2 for ages, I give up with that. We're literally just going to hardcode the templates
|
||||
// by writing them here.
|
||||
if let Platform::Android = self.build.platform() {
|
||||
self.build_android_app_dir()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(e) = success.as_ref() {
|
||||
return Err(format!("Failed to initialize build directory: {e}").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The directory in which we'll put the main exe
|
||||
///
|
||||
/// Mac, Android, Web are a little weird
|
||||
/// - mac wants to be in Contents/MacOS
|
||||
/// - android wants to be in jniLibs/arm64-v8a (or others, depending on the platform / architecture)
|
||||
/// - web wants to be in wasm (which... we don't really need to, we could just drop the wasm into public and it would work)
|
||||
///
|
||||
/// I think all others are just in the root folder
|
||||
///
|
||||
/// todo(jon): investigate if we need to put .wasm in `wasm`. It kinda leaks implementation details, which ideally we don't want to do.
|
||||
pub fn exe_dir(&self) -> PathBuf {
|
||||
match self.build.platform() {
|
||||
Platform::MacOS => self.root_dir().join("Contents").join("MacOS"),
|
||||
Platform::Web => self.root_dir().join("wasm"),
|
||||
|
||||
// Android has a whole build structure to it
|
||||
Platform::Android => self
|
||||
.root_dir()
|
||||
.join("app")
|
||||
.join("src")
|
||||
.join("main")
|
||||
.join("jniLibs")
|
||||
.join("arm64-v8a"),
|
||||
|
||||
// these are all the same, I think?
|
||||
Platform::Windows
|
||||
| Platform::Linux
|
||||
| Platform::Ios
|
||||
| Platform::Server
|
||||
| Platform::Liveview => self.root_dir(),
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the path to root build folder. This will be our working directory for the build.
|
||||
///
|
||||
/// we only add an extension to the folders where it sorta matters that it's named with the extension.
|
||||
/// for example, on mac, the `.app` indicates we can `open` it and it pulls in icons, dylibs, etc.
|
||||
///
|
||||
/// for our simulator-based platforms, this is less important since they need to be zipped up anyways
|
||||
/// to run in the simulator.
|
||||
///
|
||||
/// For windows/linux, it's also not important since we're just running the exe directly out of the folder
|
||||
///
|
||||
/// The idea of this folder is that we can run our top-level build command against it and we'll get
|
||||
/// a final build output somewhere. Some platforms have basically no build command, and can simply
|
||||
/// be ran by executing the exe directly.
|
||||
pub(crate) fn root_dir(&self) -> PathBuf {
|
||||
let platform_dir = self
|
||||
.krate
|
||||
.build_dir(self.build.platform(), self.build.release);
|
||||
|
||||
match self.build.platform() {
|
||||
Platform::Web => platform_dir.join("public"),
|
||||
Platform::Server => platform_dir.clone(), // ends up *next* to the public folder
|
||||
|
||||
// These might not actually need to be called `.app` but it does let us run these with `open`
|
||||
Platform::MacOS => platform_dir.join(format!("{}.app", self.platform_exe_name())),
|
||||
Platform::Ios => platform_dir.join(format!("{}.app", self.platform_exe_name())),
|
||||
|
||||
// in theory, these all could end up directly in the root dir
|
||||
Platform::Android => platform_dir.join("app"), // .apk (after bundling)
|
||||
Platform::Linux => platform_dir.join("app"), // .appimage (after bundling)
|
||||
Platform::Windows => platform_dir.join("app"), // .exe (after bundling)
|
||||
Platform::Liveview => platform_dir.join("app"), // .exe (after bundling)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asset_dir(&self) -> PathBuf {
|
||||
match self.build.platform() {
|
||||
Platform::MacOS => self
|
||||
.root_dir()
|
||||
.join("Contents")
|
||||
.join("Resources")
|
||||
.join("assets"),
|
||||
|
||||
Platform::Android => self
|
||||
.root_dir()
|
||||
.join("app")
|
||||
.join("src")
|
||||
.join("main")
|
||||
.join("assets"),
|
||||
|
||||
// everyone else is soooo normal, just app/assets :)
|
||||
Platform::Web
|
||||
| Platform::Ios
|
||||
| Platform::Windows
|
||||
| Platform::Linux
|
||||
| Platform::Server
|
||||
| Platform::Liveview => self.root_dir().join("assets"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn platform_exe_name(&self) -> String {
|
||||
match self.build.platform() {
|
||||
Platform::MacOS => self.krate.executable_name().to_string(),
|
||||
Platform::Ios => self.krate.executable_name().to_string(),
|
||||
Platform::Server => self.krate.executable_name().to_string(),
|
||||
Platform::Liveview => self.krate.executable_name().to_string(),
|
||||
Platform::Windows => format!("{}.exe", self.krate.executable_name()),
|
||||
|
||||
// from the apk spec, the root exe will actually be a shared library
|
||||
Platform::Android => format!("lib{}.so", self.krate.executable_name()),
|
||||
Platform::Web => unimplemented!("there's no main exe on web"), // this will be wrong, I think, but not important?
|
||||
|
||||
// todo: maybe this should be called AppRun?
|
||||
Platform::Linux => self.krate.executable_name().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_android_app_dir(&self) -> Result<()> {
|
||||
use std::fs::{create_dir_all, write};
|
||||
let root = self.root_dir();
|
||||
|
||||
// gradle
|
||||
let wrapper = root.join("gradle").join("wrapper");
|
||||
create_dir_all(&wrapper)?;
|
||||
tracing::debug!("Initialized Gradle wrapper: {:?}", wrapper);
|
||||
|
||||
// app
|
||||
let app = root.join("app");
|
||||
let app_main = app.join("src").join("main");
|
||||
let app_kotlin = app_main.join("kotlin");
|
||||
let app_jnilibs = app_main.join("jniLibs");
|
||||
let app_assets = app_main.join("assets");
|
||||
let app_kotlin_out = app_kotlin.join("com").join("example").join("androidfinal");
|
||||
create_dir_all(&app)?;
|
||||
create_dir_all(&app_main)?;
|
||||
create_dir_all(&app_kotlin)?;
|
||||
create_dir_all(&app_jnilibs)?;
|
||||
create_dir_all(&app_assets)?;
|
||||
create_dir_all(&app_kotlin_out)?;
|
||||
tracing::debug!("Initialized app: {:?}", app);
|
||||
tracing::debug!("Initialized app/src: {:?}", app_main);
|
||||
tracing::debug!("Initialized app/src/kotlin: {:?}", app_kotlin);
|
||||
tracing::debug!("Initialized app/src/jniLibs: {:?}", app_jnilibs);
|
||||
tracing::debug!("Initialized app/src/assets: {:?}", app_assets);
|
||||
tracing::debug!("Initialized app/src/kotlin/main: {:?}", app_kotlin_out);
|
||||
|
||||
// Top-level gradle config
|
||||
write(
|
||||
root.join("build.gradle.kts"),
|
||||
include_bytes!("../../assets/android/gen/build.gradle.kts"),
|
||||
)?;
|
||||
write(
|
||||
root.join("gradle.properties"),
|
||||
include_bytes!("../../assets/android/gen/gradle.properties"),
|
||||
)?;
|
||||
write(
|
||||
root.join("gradlew"),
|
||||
include_bytes!("../../assets/android/gen/gradlew"),
|
||||
)?;
|
||||
write(
|
||||
root.join("gradlew.bat"),
|
||||
include_bytes!("../../assets/android/gen/gradlew.bat"),
|
||||
)?;
|
||||
write(
|
||||
root.join("settings.gradle"),
|
||||
include_bytes!("../../assets/android/gen/settings.gradle"),
|
||||
)?;
|
||||
|
||||
// Then the wrapper and its properties
|
||||
write(
|
||||
wrapper.join("gradle-wrapper.properties"),
|
||||
include_bytes!("../../assets/android/gen/gradle/wrapper/gradle-wrapper.properties"),
|
||||
)?;
|
||||
write(
|
||||
wrapper.join("gradle-wrapper.jar"),
|
||||
include_bytes!("../../assets/android/gen/gradle/wrapper/gradle-wrapper.jar"),
|
||||
)?;
|
||||
|
||||
// Now the app directory
|
||||
write(
|
||||
app.join("build.gradle.kts"),
|
||||
include_bytes!("../../assets/android/gen/app/build.gradle.kts"),
|
||||
)?;
|
||||
write(
|
||||
app.join("proguard-rules.pro"),
|
||||
include_bytes!("../../assets/android/gen/app/proguard-rules.pro"),
|
||||
)?;
|
||||
write(
|
||||
app.join("src").join("main").join("AndroidManifest.xml"),
|
||||
include_bytes!("../../assets/android/gen/app/src/main/AndroidManifest.xml"),
|
||||
)?;
|
||||
|
||||
// Write the main activity manually since tao dropped support for it
|
||||
write(
|
||||
app_main
|
||||
.join("kotlin")
|
||||
.join("com")
|
||||
.join("example")
|
||||
.join("androidfinal")
|
||||
.join("MainActivity.kt"),
|
||||
include_bytes!("../../assets/android/MainActivity.kt"),
|
||||
)?;
|
||||
|
||||
// Write the res folder
|
||||
let res = app_main.join("res");
|
||||
create_dir_all(&res)?;
|
||||
create_dir_all(res.join("values"))?;
|
||||
write(
|
||||
res.join("values").join("strings.xml"),
|
||||
include_bytes!("../../assets/android/gen/app/src/main/res/values/strings.xml"),
|
||||
)?;
|
||||
write(
|
||||
res.join("values").join("colors.xml"),
|
||||
include_bytes!("../../assets/android/gen/app/src/main/res/values/colors.xml"),
|
||||
)?;
|
||||
write(
|
||||
res.join("values").join("styles.xml"),
|
||||
include_bytes!("../../assets/android/gen/app/src/main/res/values/styles.xml"),
|
||||
)?;
|
||||
|
||||
create_dir_all(res.join("drawable"))?;
|
||||
write(
|
||||
res.join("drawable").join("ic_launcher_background.xml"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("drawable-v24"))?;
|
||||
write(
|
||||
res.join("drawable-v24").join("ic_launcher_foreground.xml"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("mipmap-anydpi-v26"))?;
|
||||
write(
|
||||
res.join("mipmap-anydpi-v26").join("ic_launcher.xml"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("mipmap-hdpi"))?;
|
||||
write(
|
||||
res.join("mipmap-hdpi").join("ic_launcher.webp"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("mipmap-mdpi"))?;
|
||||
write(
|
||||
res.join("mipmap-mdpi").join("ic_launcher.webp"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("mipmap-xhdpi"))?;
|
||||
write(
|
||||
res.join("mipmap-xhdpi").join("ic_launcher.webp"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("mipmap-xxhdpi"))?;
|
||||
write(
|
||||
res.join("mipmap-xxhdpi").join("ic_launcher.webp"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp"
|
||||
),
|
||||
)?;
|
||||
create_dir_all(res.join("mipmap-xxxhdpi"))?;
|
||||
write(
|
||||
res.join("mipmap-xxxhdpi").join("ic_launcher.webp"),
|
||||
include_bytes!(
|
||||
"../../assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp"
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct InfoPlistData {
|
||||
pub display_name: String,
|
||||
pub bundle_name: String,
|
||||
pub bundle_identifier: String,
|
||||
pub executable_name: String,
|
||||
}
|
||||
@@ -94,21 +94,21 @@ impl BuildRequest {
|
||||
/// should be installing the x86 versions.
|
||||
pub(crate) async fn verify_ios_tooling(&self, _rustup: RustupShow) -> Result<()> {
|
||||
// open the simulator
|
||||
_ = tokio::process::Command::new("open")
|
||||
.arg("/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app")
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.status()
|
||||
.await;
|
||||
// _ = tokio::process::Command::new("open")
|
||||
// .arg("/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app")
|
||||
// .stderr(Stdio::piped())
|
||||
// .stdout(Stdio::piped())
|
||||
// .status()
|
||||
// .await;
|
||||
|
||||
// Now xcrun to open the device
|
||||
// todo: we should try and query the device list and/or parse it rather than hardcode this simulator
|
||||
_ = tokio::process::Command::new("xcrun")
|
||||
.args(["simctl", "boot", "83AE3067-987F-4F85-AE3D-7079EF48C967"])
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.status()
|
||||
.await;
|
||||
// _ = tokio::process::Command::new("xcrun")
|
||||
// .args(["simctl", "boot", "83AE3067-987F-4F85-AE3D-7079EF48C967"])
|
||||
// .stderr(Stdio::piped())
|
||||
// .stdout(Stdio::piped())
|
||||
// .status()
|
||||
// .await;
|
||||
|
||||
// if !rustup
|
||||
// .installed_toolchains
|
||||
File renamed without changes.
@@ -3,12 +3,6 @@ use crate::{
|
||||
NSISInstallerMode, NsisSettings, PackageType, WebviewInstallMode, WindowsSettings, WixSettings,
|
||||
};
|
||||
|
||||
pub(crate) fn make_tauri_bundler_settings(
|
||||
bundle_config: BundleConfig,
|
||||
) -> tauri_bundler::BundleSettings {
|
||||
bundle_config.into()
|
||||
}
|
||||
|
||||
impl From<NsisSettings> for tauri_bundler::NsisSettings {
|
||||
fn from(val: NsisSettings) -> Self {
|
||||
tauri_bundler::NsisSettings {
|
||||
@@ -162,6 +156,7 @@ impl From<PackageType> for tauri_bundler::PackageType {
|
||||
PackageType::AppImage => Self::AppImage,
|
||||
PackageType::Dmg => Self::Dmg,
|
||||
PackageType::Updater => Self::Updater,
|
||||
PackageType::Nsis => Self::Nsis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::*;
|
||||
use crate::DioxusCrate;
|
||||
use anyhow::Context;
|
||||
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||
use rayon::prelude::*;
|
||||
use std::{borrow::Cow, fs, path::Path, process::exit};
|
||||
use std::{borrow::Cow, fs, path::Path};
|
||||
|
||||
// For reference, the rustfmt main.rs file
|
||||
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
|
||||
@@ -37,7 +38,7 @@ pub(crate) struct Autoformat {
|
||||
}
|
||||
|
||||
impl Autoformat {
|
||||
pub(crate) fn autoformat(self) -> Result<()> {
|
||||
pub(crate) fn autoformat(self) -> Result<StructuredOutput> {
|
||||
let Autoformat {
|
||||
check,
|
||||
raw,
|
||||
@@ -56,9 +57,7 @@ impl Autoformat {
|
||||
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
|
||||
println!("{}", inner);
|
||||
} else {
|
||||
// exit process with error
|
||||
eprintln!("error formatting codeblock");
|
||||
exit(1);
|
||||
return Err("error formatting codeblock".into());
|
||||
}
|
||||
} else {
|
||||
// Default to formatting the project.
|
||||
@@ -68,13 +67,9 @@ impl Autoformat {
|
||||
package: Some(package),
|
||||
..Default::default()
|
||||
};
|
||||
let dx_crate = match DioxusCrate::new(&target_args) {
|
||||
Ok(x) => x,
|
||||
Err(error) => {
|
||||
eprintln!("failed to parse crate graph: {error}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let dx_crate =
|
||||
DioxusCrate::new(&target_args).context("failed to parse crate graph")?;
|
||||
|
||||
Cow::Owned(dx_crate.crate_dir())
|
||||
} else {
|
||||
Cow::Borrowed(Path::new("."))
|
||||
@@ -83,12 +78,11 @@ impl Autoformat {
|
||||
if let Err(e) =
|
||||
autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
|
||||
{
|
||||
eprintln!("error formatting project: {}", e);
|
||||
exit(1);
|
||||
return Err(format!("error formatting project: {}", e).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +100,7 @@ fn refactor_file(
|
||||
fs::read_to_string(&file)
|
||||
};
|
||||
let Ok(mut s) = file_content else {
|
||||
eprintln!("failed to open file: {}", file_content.unwrap_err());
|
||||
exit(1);
|
||||
return Err(format!("failed to open file: {}", file_content.unwrap_err()).into());
|
||||
};
|
||||
|
||||
if format_rust_code {
|
||||
@@ -117,8 +110,7 @@ fn refactor_file(
|
||||
let Ok(Ok(edits)) =
|
||||
syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent))
|
||||
else {
|
||||
eprintln!("failed to format file: {}", s);
|
||||
exit(1);
|
||||
return Err(format!("failed to format file: {}", s).into());
|
||||
};
|
||||
|
||||
let out = dioxus_autofmt::apply_formats(&s, edits);
|
||||
@@ -126,7 +118,7 @@ fn refactor_file(
|
||||
if file == "-" {
|
||||
print!("{}", out);
|
||||
} else if let Err(e) = fs::write(&file, out) {
|
||||
eprintln!("failed to write formatted content to file: {e}",);
|
||||
tracing::error!("failed to write formatted content to file: {e}",);
|
||||
} else {
|
||||
println!("formatted {}", file);
|
||||
}
|
||||
@@ -212,7 +204,7 @@ fn autoformat_project(
|
||||
match res {
|
||||
Ok(cnt) => Some(cnt),
|
||||
Err(err) => {
|
||||
eprintln!("error formatting file : {}\n{:#?}", path.display(), err);
|
||||
tracing::error!("error formatting file : {}\n{:#?}", path.display(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -222,8 +214,7 @@ fn autoformat_project(
|
||||
let files_formatted: usize = counts.into_iter().flatten().sum();
|
||||
|
||||
if files_formatted > 0 && check {
|
||||
eprintln!("{} files needed formatting", files_formatted);
|
||||
exit(1);
|
||||
return Err(format!("{} files needed formatting", files_formatted).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use super::*;
|
||||
use crate::{AppBundle, Builder, DioxusCrate, Platform, PROFILE_SERVER};
|
||||
use crate::{Builder, DioxusCrate, Platform, PROFILE_SERVER};
|
||||
|
||||
/// Build the Rust Dioxus app and all of its assets.
|
||||
///
|
||||
/// Produces a final output bundle designed to be run on the target platform.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[clap(name = "build")]
|
||||
pub(crate) struct BuildArgs {
|
||||
/// Build in release mode [default: false]
|
||||
#[clap(long, short)]
|
||||
@@ -17,21 +16,6 @@ pub(crate) struct BuildArgs {
|
||||
#[serde(default)]
|
||||
pub(crate) force_sequential: bool,
|
||||
|
||||
/// Use verbose output [default: false]
|
||||
#[clap(long)]
|
||||
#[serde(default)]
|
||||
pub(crate) verbose: bool,
|
||||
|
||||
/// Use trace output [default: false]
|
||||
#[clap(long)]
|
||||
#[serde(default)]
|
||||
pub(crate) trace: bool,
|
||||
|
||||
/// Pass -Awarnings to the cargo build
|
||||
#[clap(long)]
|
||||
#[serde(default)]
|
||||
pub(crate) silent: bool,
|
||||
|
||||
/// Build the app with custom a profile
|
||||
#[clap(long)]
|
||||
pub(crate) profile: Option<String>,
|
||||
@@ -73,12 +57,9 @@ pub(crate) struct BuildArgs {
|
||||
}
|
||||
|
||||
impl BuildArgs {
|
||||
pub async fn build_it(&mut self) -> Result<()> {
|
||||
self.build().await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn run_cmd(mut self) -> Result<StructuredOutput> {
|
||||
tracing::info!("Building project...");
|
||||
|
||||
pub(crate) async fn build(&mut self) -> Result<AppBundle> {
|
||||
let krate =
|
||||
DioxusCrate::new(&self.target_args).context("Failed to load Dioxus workspace")?;
|
||||
|
||||
@@ -86,12 +67,11 @@ impl BuildArgs {
|
||||
|
||||
let bundle = Builder::start(&krate, self.clone())?.finish().await?;
|
||||
|
||||
println!(
|
||||
"Successfully built! 💫\nBundle at {}",
|
||||
bundle.app_dir().display()
|
||||
);
|
||||
tracing::info!(path = ?bundle.build.root_dir(), "Build completed successfully! 🚀");
|
||||
|
||||
Ok(bundle)
|
||||
Ok(StructuredOutput::BuildFinished {
|
||||
path: bundle.build.root_dir(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the arguments of the CLI by inspecting the DioxusCrate itself and learning about how
|
||||
@@ -127,13 +107,13 @@ impl BuildArgs {
|
||||
// Add any features required to turn on the client
|
||||
self.target_args
|
||||
.client_features
|
||||
.extend(krate.feature_for_platform(platform));
|
||||
.push(krate.feature_for_platform(platform));
|
||||
|
||||
// Add any features required to turn on the server
|
||||
// This won't take effect in the server is not built, so it's fine to just set it here even if it's not used
|
||||
self.target_args
|
||||
.server_features
|
||||
.extend(krate.feature_for_platform(Platform::Server));
|
||||
.push(krate.feature_for_platform(Platform::Server));
|
||||
|
||||
// Make sure we set the fullstack platform so we actually build the fullstack variant
|
||||
// Users need to enable "fullstack" in their default feature set.
|
||||
@@ -150,7 +130,7 @@ impl BuildArgs {
|
||||
|
||||
// Set the profile of the build if it's not already set
|
||||
// We do this for android/wasm since they require
|
||||
if self.profile.is_none() {
|
||||
if self.profile.is_none() && !self.release {
|
||||
match self.platform {
|
||||
Some(Platform::Android) => {
|
||||
self.profile = Some(crate::dioxus_crate::PROFILE_ANDROID.to_string());
|
||||
|
||||
+164
-77
@@ -1,20 +1,36 @@
|
||||
use crate::bundle_utils::make_tauri_bundler_settings;
|
||||
use crate::DioxusCrate;
|
||||
use crate::{build::BuildArgs, PackageType};
|
||||
use crate::{AppBundle, BuildArgs, Builder, DioxusCrate, Platform};
|
||||
use anyhow::Context;
|
||||
use std::env::current_dir;
|
||||
use std::str::FromStr;
|
||||
use tauri_bundler::{PackageSettings, SettingsBuilder};
|
||||
use std::collections::HashMap;
|
||||
use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Bundle the Rust desktop app and all of its assets
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(name = "bundle")]
|
||||
pub struct Bundle {
|
||||
/// The package types to bundle
|
||||
///
|
||||
/// Any of:
|
||||
/// - macos: The macOS application bundle (.app).
|
||||
/// - ios: The iOS app bundle.
|
||||
/// - msi: The Windows bundle (.msi).
|
||||
/// - nsis: The NSIS bundle (.exe).
|
||||
/// - deb: The Linux Debian package bundle (.deb).
|
||||
/// - rpm: The Linux RPM bundle (.rpm).
|
||||
/// - appimage: The Linux AppImage bundle (.AppImage).
|
||||
/// - dmg: The macOS DMG bundle (.dmg).
|
||||
/// - updater: The Updater bundle.
|
||||
#[clap(long)]
|
||||
pub packages: Option<Vec<PackageType>>,
|
||||
pub package_types: Option<Vec<crate::PackageType>>,
|
||||
|
||||
/// The directory in which the final bundle will be placed.
|
||||
///
|
||||
/// Relative paths will be placed relative to the current working directory.
|
||||
///
|
||||
/// 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>,
|
||||
|
||||
/// The arguments for the dioxus build
|
||||
#[clap(flatten)]
|
||||
@@ -22,31 +38,124 @@ pub struct Bundle {
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
pub(crate) async fn bundle(mut self) -> anyhow::Result<()> {
|
||||
pub(crate) async fn bundle(mut self) -> Result<StructuredOutput> {
|
||||
tracing::info!("Bundling project...");
|
||||
|
||||
let krate = DioxusCrate::new(&self.build_arguments.target_args)
|
||||
.context("Failed to load Dioxus workspace")?;
|
||||
|
||||
// We always use `release` mode for bundling
|
||||
self.build_arguments.release = true;
|
||||
self.build_arguments.resolve(&krate)?;
|
||||
|
||||
// Build the app
|
||||
let bundle = self.build_arguments.build().await?;
|
||||
tracing::info!("Building app...");
|
||||
|
||||
let bundle = Builder::start(&krate, self.build_arguments.clone())?
|
||||
.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;
|
||||
|
||||
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().unwrap());
|
||||
crate::fastfs::copy_asset(&src, &dest)?;
|
||||
dest
|
||||
} else {
|
||||
src.clone()
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"{} - [{}]",
|
||||
bundle.package_type.short_name(),
|
||||
src.display()
|
||||
);
|
||||
|
||||
bundle_paths.push(src);
|
||||
}
|
||||
}
|
||||
|
||||
cmd_result = StructuredOutput::BundleOutput {
|
||||
bundles: bundle_paths,
|
||||
};
|
||||
}
|
||||
|
||||
Platform::Web => {
|
||||
tracing::info!("App available at: {}", bundle.build.root_dir().display());
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
Platform::Liveview => tracing::info!(
|
||||
"Liveview server available at: {}",
|
||||
bundle.build.root_dir().display()
|
||||
),
|
||||
|
||||
Platform::Android => {
|
||||
return Err(Error::UnsupportedFeature(
|
||||
"Android bundles are not yet supported".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(cmd_result)
|
||||
}
|
||||
|
||||
fn bundle_desktop(
|
||||
&self,
|
||||
krate: DioxusCrate,
|
||||
bundle: AppBundle,
|
||||
) -> Result<Vec<tauri_bundler::Bundle>, Error> {
|
||||
_ = std::fs::remove_dir_all(krate.bundle_dir(self.build_arguments.platform()));
|
||||
|
||||
// copy the binary to the out dir
|
||||
let package = krate.package();
|
||||
|
||||
let mut name: PathBuf = krate.executable_name().into();
|
||||
if cfg!(windows) {
|
||||
name.set_extension("exe");
|
||||
}
|
||||
std::fs::create_dir_all(krate.bundle_dir(self.build_arguments.platform()))?;
|
||||
std::fs::copy(
|
||||
&bundle.app.exe,
|
||||
krate
|
||||
.bundle_dir(self.build_arguments.platform())
|
||||
.join(krate.executable_name()),
|
||||
)?;
|
||||
|
||||
// bundle the app
|
||||
let binaries = vec![
|
||||
tauri_bundler::BundleBinary::new(name.display().to_string(), true)
|
||||
.set_src_path(Some(krate.workspace_dir().display().to_string())),
|
||||
// We use the name of the exe but it has to be in the same directory
|
||||
BundleBinary::new(name.display().to_string(), true)
|
||||
.set_src_path(Some(bundle.app.exe.display().to_string())),
|
||||
];
|
||||
|
||||
let bundle_config = krate.config.bundle.clone();
|
||||
let mut bundle_settings = make_tauri_bundler_settings(bundle_config);
|
||||
let mut bundle_settings: BundleSettings = krate.config.bundle.clone().into();
|
||||
|
||||
if cfg!(windows) {
|
||||
let windows_icon_override = krate.config.bundle.windows.as_ref().map(|w| &w.icon_path);
|
||||
@@ -62,38 +171,28 @@ impl Bundle {
|
||||
}
|
||||
}
|
||||
|
||||
// Don't copy the executable or the old bundle directory
|
||||
let ignored_files = [krate
|
||||
.bundle_dir(self.build_arguments.platform())
|
||||
.join("bundle")];
|
||||
if bundle_settings.resources_map.is_none() {
|
||||
bundle_settings.resources_map = Some(HashMap::new());
|
||||
}
|
||||
|
||||
for entry in std::fs::read_dir(bundle.asset_dir())?.flatten() {
|
||||
let path = entry.path().canonicalize()?;
|
||||
if ignored_files.iter().any(|f| path.starts_with(f)) {
|
||||
continue;
|
||||
}
|
||||
for entry in std::fs::read_dir(bundle.build.asset_dir())?.flatten() {
|
||||
let old = entry.path().canonicalize()?;
|
||||
let new = PathBuf::from("assets").join(old.file_name().unwrap());
|
||||
tracing::debug!("Bundled asset: {old:?} -> {new:?}");
|
||||
|
||||
// Tauri bundle will add a __root__ prefix if the input path is absolute even though the output path is relative?
|
||||
// We strip the prefix here to make sure the input path is relative so that the bundler puts the output path in the right place
|
||||
let path = path
|
||||
.strip_prefix(¤t_dir()?)
|
||||
.unwrap()
|
||||
.display()
|
||||
.to_string();
|
||||
if let Some(resources) = &mut bundle_settings.resources_map {
|
||||
resources.insert(path, "".to_string());
|
||||
} else {
|
||||
bundle_settings.resources_map = Some([(path, "".to_string())].into());
|
||||
}
|
||||
bundle_settings
|
||||
.resources_map
|
||||
.as_mut()
|
||||
.expect("to be set")
|
||||
.insert(old.display().to_string(), new.display().to_string());
|
||||
}
|
||||
|
||||
// Drain any resources set in the config into the resources map. Tauri bundle doesn't let you set both resources and resources_map https://github.com/DioxusLabs/dioxus/issues/2941
|
||||
for resource_path in bundle_settings.resources.take().into_iter().flatten() {
|
||||
if let Some(resources) = &mut bundle_settings.resources_map {
|
||||
resources.insert(resource_path, "".to_string());
|
||||
} else {
|
||||
bundle_settings.resources_map = Some([(resource_path, "".to_string())].into());
|
||||
}
|
||||
bundle_settings
|
||||
.resources_map
|
||||
.as_mut()
|
||||
.expect("to be set")
|
||||
.insert(resource_path, "".to_string());
|
||||
}
|
||||
|
||||
let mut settings = SettingsBuilder::new()
|
||||
@@ -106,9 +205,11 @@ impl Bundle {
|
||||
authors: Some(package.authors.clone()),
|
||||
default_run: Some(krate.executable_name().to_string()),
|
||||
})
|
||||
.log_level(log::Level::Debug)
|
||||
.binaries(binaries)
|
||||
.bundle_settings(bundle_settings);
|
||||
if let Some(packages) = &self.packages {
|
||||
|
||||
if let Some(packages) = &self.package_types {
|
||||
settings = settings.package_types(packages.iter().map(|p| (*p).into()).collect());
|
||||
}
|
||||
|
||||
@@ -116,37 +217,23 @@ impl Bundle {
|
||||
settings = settings.target(target.to_string());
|
||||
}
|
||||
|
||||
let settings = settings.build();
|
||||
|
||||
// on macos we need to set CI=true (https://github.com/tauri-apps/tauri/issues/2567)
|
||||
#[cfg(target_os = "macos")]
|
||||
std::env::set_var("CI", "true");
|
||||
|
||||
tauri_bundler::bundle::bundle_project(&settings.unwrap()).unwrap_or_else(|err|{
|
||||
#[cfg(target_os = "macos")]
|
||||
panic!("Failed to bundle project: {:#?}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
panic!("Failed to bundle project: {:#?}", err);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PackageType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"macos" => Ok(PackageType::MacOsBundle),
|
||||
"ios" => Ok(PackageType::IosBundle),
|
||||
"msi" => Ok(PackageType::WindowsMsi),
|
||||
"deb" => Ok(PackageType::Deb),
|
||||
"rpm" => Ok(PackageType::Rpm),
|
||||
"appimage" => Ok(PackageType::AppImage),
|
||||
"dmg" => Ok(PackageType::Dmg),
|
||||
"updater" => Ok(PackageType::Updater),
|
||||
_ => Err(format!("{} is not a valid package type", s)),
|
||||
if self.build_arguments.platform() == Platform::Ios {
|
||||
settings = settings.target("aarch64-apple-ios".to_string());
|
||||
}
|
||||
|
||||
let settings = settings.build()?;
|
||||
tracing::debug!("Bundling project with settings: {:#?}", settings);
|
||||
if cfg!(target_os = "macos") {
|
||||
std::env::set_var("CI", "true");
|
||||
}
|
||||
|
||||
let bundles = tauri_bundler::bundle::bundle_project(&settings).inspect_err(|err| {
|
||||
tracing::error!("Failed to bundle project: {:#?}", err);
|
||||
if cfg!(target_os = "macos") {
|
||||
tracing::error!("Make sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)");
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(bundles)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
//! Run linting against the user's codebase.
|
||||
//!
|
||||
//! For reference, the rustfmt main.rs file
|
||||
//! https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
|
||||
|
||||
use super::*;
|
||||
use crate::DioxusCrate;
|
||||
use anyhow::Context;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use std::{path::Path, process::exit};
|
||||
|
||||
// For reference, the rustfmt main.rs file
|
||||
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
|
||||
use std::path::Path;
|
||||
|
||||
/// Check the Rust files in the project for issues.
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
@@ -20,25 +23,23 @@ pub(crate) struct Check {
|
||||
|
||||
impl Check {
|
||||
// Todo: check the entire crate
|
||||
pub(crate) async fn check(self) -> Result<()> {
|
||||
pub(crate) async fn check(self) -> Result<StructuredOutput> {
|
||||
match self.file {
|
||||
// Default to checking the project
|
||||
None => {
|
||||
let dioxus_crate = DioxusCrate::new(&self.target_args)?;
|
||||
if let Err(e) = check_project_and_report(dioxus_crate).await {
|
||||
eprintln!("error checking project: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
check_project_and_report(dioxus_crate)
|
||||
.await
|
||||
.context("error checking project")?;
|
||||
}
|
||||
Some(file) => {
|
||||
if let Err(e) = check_file_and_report(file).await {
|
||||
eprintln!("failed to check file: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
check_file_and_report(file)
|
||||
.await
|
||||
.context("error checking file")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
|
||||
.await;
|
||||
|
||||
if res.is_err() {
|
||||
eprintln!("error checking file: {}", path.display());
|
||||
tracing::error!("error checking file: {}", path.display());
|
||||
}
|
||||
|
||||
res
|
||||
@@ -92,19 +93,17 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
|
||||
|
||||
for report in issue_reports.into_iter() {
|
||||
if !report.issues.is_empty() {
|
||||
println!("{}", report);
|
||||
tracing::info!("{}", report);
|
||||
}
|
||||
}
|
||||
|
||||
match total_issues {
|
||||
0 => println!("No issues found."),
|
||||
1 => println!("1 issue found."),
|
||||
_ => println!("{} issues found.", total_issues),
|
||||
}
|
||||
|
||||
match total_issues {
|
||||
0 => exit(0),
|
||||
_ => exit(1),
|
||||
0 => {
|
||||
tracing::info!("No issues found.");
|
||||
Ok(())
|
||||
}
|
||||
1 => Err("1 issue found.".into()),
|
||||
_ => Err(format!("{} issues found.", total_issues).into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +113,6 @@ fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
|
||||
};
|
||||
|
||||
// load the gitignore
|
||||
|
||||
for entry in folder {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
use super::*;
|
||||
use crate::Result;
|
||||
|
||||
/// Clean build artifacts.
|
||||
///
|
||||
/// Simlpy runs `cargo clean`
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(name = "clean")]
|
||||
pub(crate) struct Clean {}
|
||||
|
||||
impl Clean {
|
||||
/// todo(jon): we should add a config option that just wipes target/dx and target/dioxus-client instead of doing a full clean
|
||||
pub(crate) fn clean(self) -> anyhow::Result<()> {
|
||||
let output = Command::new("cargo")
|
||||
pub(crate) async fn clean(self) -> Result<StructuredOutput> {
|
||||
let output = tokio::process::Command::new("cargo")
|
||||
.arg("clean")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()?;
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Cargo clean failed."));
|
||||
return Err(anyhow::anyhow!("Cargo clean failed.").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use crate::{metadata::crate_root, CliSettings};
|
||||
|
||||
/// Dioxus config file controls
|
||||
#[derive(Clone, Debug, Deserialize, Subcommand)]
|
||||
#[clap(name = "config")]
|
||||
pub(crate) enum Config {
|
||||
/// Init `Dioxus.toml` for project/folder.
|
||||
Init {
|
||||
@@ -76,7 +75,7 @@ impl From<BoolValue> for bool {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn config(self) -> Result<()> {
|
||||
pub(crate) fn config(self) -> Result<StructuredOutput> {
|
||||
let crate_root = crate_root()?;
|
||||
match self {
|
||||
Config::Init {
|
||||
@@ -89,7 +88,7 @@ impl Config {
|
||||
tracing::warn!(
|
||||
"config file `Dioxus.toml` already exist, use `--force` to overwrite it."
|
||||
);
|
||||
return Ok(());
|
||||
return Ok(StructuredOutput::Success);
|
||||
}
|
||||
let mut file = File::create(conf_path)?;
|
||||
let content = String::from(include_str!("../../assets/dioxus.toml"))
|
||||
@@ -99,7 +98,7 @@ impl Config {
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Init config file completed.");
|
||||
}
|
||||
Config::FormatPrint {} => {
|
||||
println!(
|
||||
tracing::info!(
|
||||
"{:#?}",
|
||||
crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default())?.config
|
||||
);
|
||||
@@ -112,7 +111,7 @@ impl Config {
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Create custom html file done.");
|
||||
}
|
||||
Config::LogFile {} => {
|
||||
let log_path = crate::tracer::log_path();
|
||||
let log_path = crate::logging::FileAppendLayer::log_path();
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Log file is located at {}", log_path.display());
|
||||
}
|
||||
// Handle CLI settings.
|
||||
@@ -132,6 +131,7 @@ impl Config {
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 CLI setting `{setting}` has been set.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use std::path::Path;
|
||||
pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template";
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[clap(name = "new")]
|
||||
pub(crate) struct Create {
|
||||
/// Project name (required when `--yes` is used)
|
||||
name: Option<String>,
|
||||
@@ -39,7 +38,7 @@ pub(crate) struct Create {
|
||||
}
|
||||
|
||||
impl Create {
|
||||
pub(crate) fn create(mut self) -> Result<()> {
|
||||
pub(crate) fn create(mut self) -> Result<StructuredOutput> {
|
||||
let metadata = cargo_metadata::MetadataCommand::new().exec().ok();
|
||||
|
||||
// If we're getting pass a `.` name, that's actually a path
|
||||
@@ -106,7 +105,9 @@ impl Create {
|
||||
.expect("ctrlc::set_handler");
|
||||
let path = cargo_generate::generate(args)?;
|
||||
|
||||
post_create(&path, metadata)
|
||||
post_create(&path, metadata)?;
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{Result, StructuredOutput};
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct Doctor {}
|
||||
|
||||
impl Doctor {
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
pub async fn run(self) -> Result<StructuredOutput> {
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,27 @@ use crate::cli::create::DEFAULT_TEMPLATE;
|
||||
use cargo_generate::{GenerateArgs, TemplatePath};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[clap(name = "init")]
|
||||
pub(crate) struct Init {
|
||||
/// Template path
|
||||
#[clap(default_value = DEFAULT_TEMPLATE, short, long)]
|
||||
template: String,
|
||||
|
||||
/// Pass <option>=<value> for the used template (e.g., `foo=bar`)
|
||||
#[clap(short, long)]
|
||||
option: Vec<String>,
|
||||
|
||||
/// Specify a sub-template within the template repository to be used as the actual template
|
||||
#[clap(long)]
|
||||
subtemplate: Option<String>,
|
||||
|
||||
/// Skip user interaction by using the default values for the used template.
|
||||
/// Default values can be overridden with `--option`
|
||||
#[clap(short, long)]
|
||||
yes: bool,
|
||||
// TODO: turn on/off cargo-generate's output (now is invisible)
|
||||
// #[clap(default_value = "false", short, long)]
|
||||
// silent: bool,
|
||||
}
|
||||
|
||||
impl Init {
|
||||
pub(crate) fn init(self) -> Result<()> {
|
||||
pub(crate) fn init(self) -> Result<StructuredOutput> {
|
||||
let metadata = cargo_metadata::MetadataCommand::new().exec().ok();
|
||||
|
||||
// Get directory name.
|
||||
@@ -57,6 +56,8 @@ impl Init {
|
||||
..Default::default()
|
||||
};
|
||||
let path = cargo_generate::generate(args)?;
|
||||
create::post_create(&path, metadata)
|
||||
create::post_create(&path, metadata)?;
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::assets::AssetManifest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::assets::AssetManifest;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum LinkAction {
|
||||
BuildAssetManifest {
|
||||
@@ -15,7 +14,7 @@ pub enum LinkAction {
|
||||
}
|
||||
|
||||
impl LinkAction {
|
||||
pub(crate) const ENV_VAR_NAME: &'static str = "dx-magic-link-file";
|
||||
pub(crate) const ENV_VAR_NAME: &'static str = "dx_magic_link_file";
|
||||
|
||||
/// Should we write the input arguments to a file (aka act as a linker subprocess)?
|
||||
///
|
||||
@@ -39,7 +38,7 @@ impl LinkAction {
|
||||
///
|
||||
/// hmmmmmmmm tbh I'd rather just pass the object files back and do the parsing here, but the interface
|
||||
/// is nicer to just bounce back the args and let the host do the parsing/canonicalization
|
||||
pub(crate) fn run(self) -> anyhow::Result<()> {
|
||||
pub(crate) fn run(self) {
|
||||
match self {
|
||||
// Literally just run the android linker :)
|
||||
LinkAction::LinkAndroid {
|
||||
@@ -102,7 +101,5 @@ impl LinkAction {
|
||||
std::fs::write(dest, contents).expect("Failed to write output file");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -12,12 +12,14 @@ pub(crate) mod run;
|
||||
pub(crate) mod serve;
|
||||
pub(crate) mod target;
|
||||
pub(crate) mod translate;
|
||||
pub(crate) mod verbosity;
|
||||
|
||||
pub(crate) use build::*;
|
||||
pub(crate) use serve::*;
|
||||
pub(crate) use target::*;
|
||||
pub(crate) use verbosity::*;
|
||||
|
||||
use crate::{error::Result, Error};
|
||||
use crate::{error::Result, Error, StructuredOutput};
|
||||
use anyhow::Context;
|
||||
use clap::{Parser, Subcommand};
|
||||
use html_parser::Dom;
|
||||
@@ -35,40 +37,42 @@ use std::{
|
||||
#[derive(Parser)]
|
||||
#[clap(name = "dioxus", version = VERSION.as_str())]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
#[command(subcommand)]
|
||||
pub(crate) action: Commands,
|
||||
|
||||
/// Enable verbose logging.
|
||||
#[clap(short)]
|
||||
pub(crate) v: bool,
|
||||
|
||||
/// Specify a binary target.
|
||||
#[clap(global = true, long)]
|
||||
pub(crate) bin: Option<String>,
|
||||
#[command(flatten)]
|
||||
pub(crate) verbosity: Verbosity,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Build the Dioxus project and all of its assets.
|
||||
#[clap(name = "build")]
|
||||
Build(build::BuildArgs),
|
||||
|
||||
/// Translate a source file into Dioxus code.
|
||||
#[clap(name = "translate")]
|
||||
Translate(translate::Translate),
|
||||
|
||||
/// Build, watch & serve the Dioxus project and all of its assets.
|
||||
#[clap(name = "serve")]
|
||||
Serve(serve::ServeArgs),
|
||||
|
||||
/// Create a new project for Dioxus.
|
||||
#[clap(name = "new")]
|
||||
New(create::Create),
|
||||
|
||||
/// Init a new project for Dioxus in an existing directory.
|
||||
/// Will attempt to keep your project in a good state.
|
||||
#[clap(name = "init")]
|
||||
Init(init::Init),
|
||||
|
||||
/// Clean output artifacts.
|
||||
#[clap(name = "clean")]
|
||||
Clean(clean::Clean),
|
||||
|
||||
/// Bundle the Dioxus app into a shippable object.
|
||||
#[clap(name = "bundle")]
|
||||
Bundle(bundle::Bundle),
|
||||
|
||||
/// Automatically format RSX.
|
||||
@@ -89,6 +93,7 @@ pub(crate) enum Commands {
|
||||
|
||||
/// Dioxus config file controls.
|
||||
#[clap(subcommand)]
|
||||
#[clap(name = "config")]
|
||||
Config(config::Config),
|
||||
}
|
||||
|
||||
@@ -111,7 +116,7 @@ impl Display for Commands {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static VERSION: Lazy<String> = Lazy::new(|| {
|
||||
static VERSION: Lazy<String> = Lazy::new(|| {
|
||||
format!(
|
||||
"{} ({})",
|
||||
crate::dx_build_info::PKG_VERSION,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::{serve::ServeUpdate, BuildArgs, Builder, DioxusCrate};
|
||||
use crate::{serve::ServeUpdate, BuildArgs, Builder, DioxusCrate, Result};
|
||||
|
||||
/// Run the project with the given arguments
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
@@ -10,14 +10,14 @@ pub(crate) struct RunArgs {
|
||||
}
|
||||
|
||||
impl RunArgs {
|
||||
pub(crate) async fn run(mut self) -> anyhow::Result<()> {
|
||||
pub(crate) async fn run(mut self) -> Result<StructuredOutput> {
|
||||
let krate = DioxusCrate::new(&self.build_args.target_args)
|
||||
.context("Failed to load Dioxus workspace")?;
|
||||
|
||||
self.build_args.resolve(&krate)?;
|
||||
|
||||
println!("Building crate krate data: {:#?}", krate);
|
||||
println!("Build args: {:#?}", self.build_args);
|
||||
tracing::trace!("Building crate krate data: {:#?}", krate);
|
||||
tracing::trace!("Build args: {:#?}", self.build_args);
|
||||
|
||||
let bundle = Builder::start(&krate, self.build_args.clone())?
|
||||
.finish()
|
||||
@@ -35,11 +35,15 @@ impl RunArgs {
|
||||
// They won't generally be emitted
|
||||
loop {
|
||||
match runner.wait().await {
|
||||
ServeUpdate::StderrReceived { platform, msg } => println!("[{platform}]: {msg}"),
|
||||
ServeUpdate::StdoutReceived { platform, msg } => println!("[{platform}]: {msg}"),
|
||||
ServeUpdate::StderrReceived { platform, msg } => {
|
||||
tracing::info!("[{platform}]: {msg}")
|
||||
}
|
||||
ServeUpdate::StdoutReceived { platform, msg } => {
|
||||
tracing::info!("[{platform}]: {msg}")
|
||||
}
|
||||
ServeUpdate::ProcessExited { platform, status } => {
|
||||
runner.kill(platform);
|
||||
eprintln!("[{platform}]: process exited with status: {status:?}");
|
||||
tracing::info!("[{platform}]: process exited with status: {status:?}");
|
||||
break;
|
||||
}
|
||||
ServeUpdate::BuildUpdate { .. } => {}
|
||||
@@ -55,6 +59,6 @@ impl RunArgs {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
use super::*;
|
||||
use crate::{AddressArguments, BuildArgs, DioxusCrate, Platform};
|
||||
use anyhow::Context;
|
||||
|
||||
/// Serve the project
|
||||
#[derive(Clone, Debug, Default, Parser)]
|
||||
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
|
||||
#[clap(name = "serve")]
|
||||
pub(crate) struct ServeArgs {
|
||||
/// The arguments for the address the server will run on
|
||||
#[clap(flatten)]
|
||||
@@ -47,10 +45,22 @@ pub(crate) struct ServeArgs {
|
||||
|
||||
impl ServeArgs {
|
||||
/// Start the tui, builder, etc by resolving the arguments and then running the actual top-level serve function
|
||||
pub(crate) async fn serve(mut self) -> Result<()> {
|
||||
let krate = DioxusCrate::new(&self.build_arguments.target_args)
|
||||
.context("Failed to load Dioxus workspace")?;
|
||||
///
|
||||
/// Make sure not to do any intermediate logging since our tracing infra has now enabled much
|
||||
/// higher log levels
|
||||
pub(crate) async fn serve(self) -> Result<StructuredOutput> {
|
||||
crate::serve::serve_all(self).await?;
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
|
||||
pub(crate) fn load_krate(&mut self) -> Result<DioxusCrate> {
|
||||
let krate = DioxusCrate::new(&self.build_arguments.target_args)?;
|
||||
self.resolve(&krate)?;
|
||||
Ok(krate)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> {
|
||||
// Enable hot reload.
|
||||
if self.hot_reload.is_none() {
|
||||
self.hot_reload = Some(krate.settings.always_hot_reload.unwrap_or(true));
|
||||
@@ -72,9 +82,9 @@ impl ServeArgs {
|
||||
}
|
||||
|
||||
// Resolve the build arguments
|
||||
self.build_arguments.resolve(&krate)?;
|
||||
self.build_arguments.resolve(krate)?;
|
||||
|
||||
crate::serve::serve_all(self, krate).await
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn should_hotreload(&self) -> bool {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use super::*;
|
||||
use crate::{Result, TraceSrc};
|
||||
use crate::{Result, StructuredOutput};
|
||||
use dioxus_rsx::{BodyNode, CallBody, TemplateBody};
|
||||
use std::{io::IsTerminal as _, process::exit};
|
||||
|
||||
/// Translate some source file into Dioxus code
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(name = "translate")]
|
||||
pub(crate) struct Translate {
|
||||
/// Activate debug mode
|
||||
// short and long flags (-d, --debug) will be deduced from the field's name
|
||||
@@ -26,7 +24,7 @@ pub(crate) struct Translate {
|
||||
}
|
||||
|
||||
impl Translate {
|
||||
pub(crate) fn translate(self) -> Result<()> {
|
||||
pub(crate) fn translate(self) -> Result<StructuredOutput> {
|
||||
// Get the right input for the translation
|
||||
let contents = determine_input(self.file, self.raw)?;
|
||||
|
||||
@@ -34,15 +32,18 @@ impl Translate {
|
||||
let dom = html_parser::Dom::parse(&contents)?;
|
||||
|
||||
// Convert the HTML to RSX
|
||||
let out = convert_html_to_formatted_rsx(&dom, self.component);
|
||||
let html = convert_html_to_formatted_rsx(&dom, self.component);
|
||||
|
||||
// Write the output
|
||||
// todo(jon): we should probably use tracing out a different output format
|
||||
// right now we're just printing to stdout since some tools rely on that, but likely we don't want that
|
||||
// instead we should be printing as json (or maybe even a different format) if we're not interactive
|
||||
match self.output {
|
||||
Some(output) => std::fs::write(output, out)?,
|
||||
None => print!("{}", out),
|
||||
Some(output) => std::fs::write(output, &html)?,
|
||||
None => print!("{}", html),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::HtmlTranslate { html })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +104,11 @@ fn indent_and_write(raw: &str, idx: usize, out: &mut String) {
|
||||
}
|
||||
|
||||
fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String> {
|
||||
use std::io::IsTerminal as _;
|
||||
|
||||
// Make sure not both are specified
|
||||
if file.is_some() && raw.is_some() {
|
||||
tracing::error!(dx_src = ?TraceSrc::Dev, "Only one of --file or --raw should be specified.");
|
||||
exit(0);
|
||||
return Err("Only one of --file or --raw should be specified.".into());
|
||||
}
|
||||
|
||||
if let Some(raw) = raw {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Clone, Copy, Debug)]
|
||||
pub struct Verbosity {
|
||||
/// Use verbose output [default: false]
|
||||
#[clap(long, global = true)]
|
||||
pub(crate) verbose: bool,
|
||||
|
||||
/// Use trace output [default: false]
|
||||
#[clap(long, global = true)]
|
||||
pub(crate) trace: bool,
|
||||
|
||||
/// Output logs in JSON format
|
||||
#[clap(long, global = true)]
|
||||
pub(crate) json_output: bool,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub(crate) struct BundleConfig {
|
||||
@@ -181,18 +181,6 @@ impl Default for WebviewInstallMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum PackageType {
|
||||
MacOsBundle,
|
||||
IosBundle,
|
||||
WindowsMsi,
|
||||
Deb,
|
||||
Rpm,
|
||||
AppImage,
|
||||
Dmg,
|
||||
Updater,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomSignCommandSettings {
|
||||
/// The command to run to sign the binary.
|
||||
@@ -202,3 +190,35 @@ pub struct CustomSignCommandSettings {
|
||||
/// "%1" will be replaced with the path to the binary to be signed.
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum PackageType {
|
||||
MacOsBundle,
|
||||
IosBundle,
|
||||
WindowsMsi,
|
||||
Nsis,
|
||||
Deb,
|
||||
Rpm,
|
||||
AppImage,
|
||||
Dmg,
|
||||
Updater,
|
||||
}
|
||||
|
||||
impl FromStr for PackageType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"macos" => Ok(PackageType::MacOsBundle),
|
||||
"ios" => Ok(PackageType::IosBundle),
|
||||
"msi" => Ok(PackageType::WindowsMsi),
|
||||
"nsis" => Ok(PackageType::Nsis),
|
||||
"deb" => Ok(PackageType::Deb),
|
||||
"rpm" => Ok(PackageType::Rpm),
|
||||
"appimage" => Ok(PackageType::AppImage),
|
||||
"dmg" => Ok(PackageType::Dmg),
|
||||
"updater" => Ok(PackageType::Updater),
|
||||
_ => Err(format!("{} is not a valid package type", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,15 @@ pub(crate) static PROFILE_SERVER: &str = "dioxus-server";
|
||||
|
||||
impl DioxusCrate {
|
||||
pub(crate) fn new(target: &TargetArgs) -> Result<Self> {
|
||||
let mut cmd = Cmd::new();
|
||||
cmd.features(target.features.clone());
|
||||
|
||||
let krates = krates::Builder::new()
|
||||
tracing::debug!("Loading crate");
|
||||
let cmd = Cmd::new();
|
||||
let builder = krates::Builder::new();
|
||||
let krates = builder
|
||||
.build(cmd, |_| {})
|
||||
.context("Failed to run cargo metadata")?;
|
||||
|
||||
let package = find_main_package(&krates, target.package.clone())?;
|
||||
tracing::debug!("Found package {package:?}");
|
||||
|
||||
let dioxus_config = DioxusConfig::load(&krates, package)?.unwrap_or_default();
|
||||
|
||||
@@ -173,6 +174,7 @@ impl DioxusCrate {
|
||||
.get_enabled_features(krate.kid)?
|
||||
.iter()
|
||||
.flat_map(|feature| {
|
||||
tracing::trace!("Autodetecting platform from feature {feature}");
|
||||
Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -230,8 +232,6 @@ impl DioxusCrate {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -246,13 +246,13 @@ impl DioxusCrate {
|
||||
}
|
||||
|
||||
/// Get the features required to build for the given platform
|
||||
pub(crate) fn feature_for_platform(&self, platform: Platform) -> Option<String> {
|
||||
pub(crate) fn feature_for_platform(&self, platform: Platform) -> 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)| {
|
||||
let res = 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());
|
||||
@@ -271,7 +271,16 @@ impl DioxusCrate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
res.unwrap_or_else(|| {
|
||||
let fallback = format!("dioxus/{}", platform.feature_name()) ;
|
||||
tracing::debug!(
|
||||
"Could not find explicit feature for platform {platform}, passing `fallback` instead"
|
||||
);
|
||||
fallback
|
||||
})
|
||||
}
|
||||
|
||||
@@ -312,14 +321,13 @@ impl DioxusCrate {
|
||||
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()));
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
@@ -561,59 +569,60 @@ impl std::fmt::Debug for DioxusCrate {
|
||||
|
||||
// Find the main package in the workspace
|
||||
fn find_main_package(krates: &Krates, package: Option<String>) -> Result<NodeId> {
|
||||
let kid = match package {
|
||||
Some(package) => {
|
||||
let mut workspace_members = 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() {
|
||||
eprintln!("Could not find package {package} in the workspace. Did you forget to add it to the workspace?");
|
||||
eprintln!("Packages in the workspace:");
|
||||
for package in krates.workspace_members() {
|
||||
if let krates::Node::Krate { krate, .. } = package {
|
||||
eprintln!("{}", krate.name());
|
||||
}
|
||||
if let Some(package) = package {
|
||||
let mut workspace_members = 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
|
||||
});
|
||||
|
||||
found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?
|
||||
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 krates.workspace_members() {
|
||||
if let krates::Node::Krate { krate, .. } = package {
|
||||
tracing::error!("{}", krate.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// 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 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 = found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?;
|
||||
|
||||
return Ok(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 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
closest_parent
|
||||
.map(|(id, _)| id)
|
||||
.context("Failed to find current package")?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let kid = closest_parent
|
||||
.map(|(id, _)| id)
|
||||
.context("Failed to find current package")?;
|
||||
|
||||
let package = krates.nid_for_kid(kid).unwrap();
|
||||
Ok(package)
|
||||
|
||||
@@ -30,6 +30,15 @@ pub(crate) enum Error {
|
||||
#[error("Failed to establish proxy: {0}")]
|
||||
ProxySetup(String),
|
||||
|
||||
#[error("Failed to bundle project: {0}")]
|
||||
BundleFailed(#[from] tauri_bundler::Error),
|
||||
|
||||
#[error("Unsupported feature: {0}")]
|
||||
UnsupportedFeature(String),
|
||||
|
||||
#[error("Failed to render template: {0}")]
|
||||
TemplateParse(#[from] handlebars::RenderError),
|
||||
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
//! 3. Build CLI layer for routing tracing logs to the TUI.
|
||||
//! 4. Build fmt layer for non-interactive logging with a custom writer that prevents output during interactive mode.
|
||||
|
||||
use crate::{serve::ServeUpdate, Platform as TargetPlatform};
|
||||
use crate::{serve::ServeUpdate, Cli, Commands, Platform as TargetPlatform, Verbosity};
|
||||
use cargo_metadata::{diagnostic::DiagnosticLevel, CompilerMessage};
|
||||
use clap::Parser;
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{
|
||||
@@ -23,37 +24,107 @@ use std::{
|
||||
env,
|
||||
fmt::{Debug, Display, Write as _},
|
||||
fs,
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
sync::Mutex,
|
||||
time::Instant,
|
||||
};
|
||||
use tracing::{field::Visit, Level, Subscriber};
|
||||
use tracing_subscriber::{fmt::format, prelude::*, registry::LookupSpan, EnvFilter, Layer};
|
||||
use tracing_subscriber::{
|
||||
fmt::{
|
||||
format::{self, Writer},
|
||||
time::FormatTime,
|
||||
},
|
||||
prelude::*,
|
||||
registry::LookupSpan,
|
||||
EnvFilter, Layer,
|
||||
};
|
||||
|
||||
const LOG_ENV: &str = "DIOXUS_LOG";
|
||||
const LOG_FILE_NAME: &str = "dx.log";
|
||||
const DX_SRC_FLAG: &str = "dx_src";
|
||||
|
||||
pub fn log_path() -> PathBuf {
|
||||
let tmp_dir = std::env::temp_dir();
|
||||
tmp_dir.join(LOG_FILE_NAME)
|
||||
}
|
||||
|
||||
static TUI_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
static TUI_TX: OnceCell<UnboundedSender<TraceMsg>> = OnceCell::new();
|
||||
pub static VERBOSITY: OnceCell<Verbosity> = OnceCell::new();
|
||||
|
||||
pub(crate) struct TraceController {
|
||||
pub(crate) tui_rx: UnboundedReceiver<TraceMsg>,
|
||||
}
|
||||
|
||||
impl TraceController {
|
||||
/// Initialize the CLI and set up the tracing infrastructure
|
||||
pub fn initialize() -> Cli {
|
||||
let args = Cli::parse();
|
||||
|
||||
VERBOSITY
|
||||
.set(args.verbosity)
|
||||
.expect("verbosity should only be set once");
|
||||
|
||||
// When running in interactive mode (of which serve is the only one), we want to do things slightly differently
|
||||
// This involves no fmt layer or file logging
|
||||
if matches!(args.action, Commands::Serve(_)) {
|
||||
Self::initialize_for_serve();
|
||||
return args;
|
||||
}
|
||||
|
||||
// By default we capture ourselves at a higher tracing level when serving
|
||||
// This ensures we're tracing ourselves even if we end up tossing the logs
|
||||
let filter = if env::var(LOG_ENV).is_ok() {
|
||||
EnvFilter::from_env(LOG_ENV)
|
||||
} else {
|
||||
EnvFilter::new(format!(
|
||||
"error,dx={our_level},dioxus-cli={our_level},manganis-cli-support={our_level}",
|
||||
our_level = if args.verbosity.verbose {
|
||||
"debug"
|
||||
} else {
|
||||
"info"
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
let json_filter = tracing_subscriber::filter::filter_fn(move |meta| {
|
||||
if meta.fields().len() == 1 && meta.fields().iter().next().unwrap().name() == "json" {
|
||||
return args.verbosity.json_output;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_target(args.verbosity.verbose)
|
||||
.fmt_fields(
|
||||
format::debug_fn(move |writer, field, value| {
|
||||
if field.name() == "json" && !args.verbosity.json_output {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
write!(writer, "{}", format_field(field.name(), value))
|
||||
})
|
||||
.delimited(" "),
|
||||
)
|
||||
.with_timer(PrettyUptime::default());
|
||||
|
||||
let fmt_layer = if args.verbosity.json_output {
|
||||
fmt_layer.json().flatten_event(true).boxed()
|
||||
} else {
|
||||
fmt_layer.boxed()
|
||||
};
|
||||
|
||||
let sub = tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(json_filter)
|
||||
.with(FileAppendLayer::new())
|
||||
.with(fmt_layer);
|
||||
|
||||
#[cfg(feature = "tokio-console")]
|
||||
let sub = sub.with(console_subscriber::spawn());
|
||||
|
||||
sub.init();
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
/// Get a handle to the trace controller.
|
||||
pub fn redirect() -> Self {
|
||||
let (tui_tx, tui_rx) = unbounded();
|
||||
TUI_ENABLED.store(true, Ordering::SeqCst);
|
||||
TUI_TX.set(tui_tx.clone()).unwrap();
|
||||
Self { tui_rx }
|
||||
}
|
||||
@@ -65,54 +136,13 @@ impl TraceController {
|
||||
ServeUpdate::TracingLog { log }
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&self) {
|
||||
TUI_ENABLED.store(false, Ordering::SeqCst);
|
||||
}
|
||||
fn initialize_for_serve() {
|
||||
let filter = EnvFilter::new("error,dx=trace,dioxus-cli=trace,manganis-cli-support=trace");
|
||||
|
||||
/// Build tracing infrastructure.
|
||||
pub fn initialize() {
|
||||
let mut filter =
|
||||
EnvFilter::new("error,dx=trace,dioxus-cli=debug,manganis-cli-support=debug");
|
||||
|
||||
if env::var(LOG_ENV).is_ok() {
|
||||
filter = EnvFilter::from_env(LOG_ENV);
|
||||
}
|
||||
|
||||
// Log file
|
||||
let log_path = log_path();
|
||||
_ = std::fs::write(&log_path, "");
|
||||
let file_append_layer = match FileAppendLayer::new(log_path) {
|
||||
Ok(f) => Some(f),
|
||||
Err(e) => {
|
||||
tracing::error!(dx_src = ?TraceSrc::Dev, err = ?e, "failed to init log file");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Build CLI layer
|
||||
let cli_layer = CLILayer;
|
||||
|
||||
// Build fmt layer
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.fmt_fields(
|
||||
format::debug_fn(|writer, field, value| {
|
||||
write!(writer, "{}", format_field(field.name(), value))
|
||||
})
|
||||
.delimited(" "),
|
||||
)
|
||||
.with_writer(Mutex::new(FmtLogWriter {}))
|
||||
.with_timer(tracing_subscriber::fmt::time::time());
|
||||
|
||||
let sub = tracing_subscriber::registry()
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(file_append_layer)
|
||||
.with(cli_layer)
|
||||
.with(fmt_layer);
|
||||
|
||||
#[cfg(feature = "tokio-console")]
|
||||
let sub = sub.with(console_subscriber::spawn());
|
||||
|
||||
sub.init();
|
||||
.with(CLILayer {})
|
||||
.init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,17 +150,27 @@ impl TraceController {
|
||||
///
|
||||
/// This layer returns on any error allowing the cli to continue work
|
||||
/// despite failing to log to a file. This helps in case of permission errors and similar.
|
||||
struct FileAppendLayer {
|
||||
pub(crate) struct FileAppendLayer {
|
||||
file_path: PathBuf,
|
||||
buffer: Mutex<String>,
|
||||
}
|
||||
|
||||
impl FileAppendLayer {
|
||||
pub fn new(file_path: PathBuf) -> io::Result<Self> {
|
||||
Ok(Self {
|
||||
fn new() -> Self {
|
||||
let file_path = Self::log_path();
|
||||
|
||||
if !file_path.exists() {
|
||||
_ = std::fs::write(&file_path, "");
|
||||
}
|
||||
|
||||
Self {
|
||||
file_path,
|
||||
buffer: Mutex::new(String::new()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_path() -> PathBuf {
|
||||
std::env::temp_dir().join(LOG_FILE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,15 +234,6 @@ where
|
||||
let mut visitor = CollectVisitor::new();
|
||||
event.record(&mut visitor);
|
||||
|
||||
// If the TUI output is disabled we let fmt subscriber handle the logs
|
||||
// EXCEPT for cargo logs which we just print.
|
||||
if !TUI_ENABLED.load(Ordering::SeqCst) {
|
||||
if visitor.source == TraceSrc::Cargo {
|
||||
println!("{}", visitor.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let meta = event.metadata();
|
||||
let level = meta.level();
|
||||
|
||||
@@ -217,14 +248,11 @@ where
|
||||
visitor.source = TraceSrc::Dev;
|
||||
}
|
||||
|
||||
TUI_TX
|
||||
_ = TUI_TX
|
||||
.get()
|
||||
.unwrap()
|
||||
.unbounded_send(TraceMsg::text(visitor.source, *level, final_msg))
|
||||
.unwrap();
|
||||
.unbounded_send(TraceMsg::text(visitor.source, *level, final_msg));
|
||||
}
|
||||
|
||||
// TODO: support spans? structured tui log display?
|
||||
}
|
||||
|
||||
/// A record visitor that collects dx-specific info and user-provided fields for logging consumption.
|
||||
@@ -239,7 +267,6 @@ impl CollectVisitor {
|
||||
Self {
|
||||
message: String::new(),
|
||||
source: TraceSrc::Unknown,
|
||||
|
||||
fields: HashMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -266,26 +293,6 @@ impl Visit for CollectVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
struct FmtLogWriter {}
|
||||
|
||||
impl Write for FmtLogWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if !TUI_ENABLED.load(Ordering::SeqCst) {
|
||||
return std::io::stdout().write(buf);
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
if !TUI_ENABLED.load(Ordering::SeqCst) {
|
||||
std::io::stdout().flush()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a tracing field and value, removing any internal fields from the final output.
|
||||
fn format_field(field_name: &str, value: &dyn Debug) -> String {
|
||||
let mut out = String::new();
|
||||
@@ -398,3 +405,26 @@ impl Display for TraceSrc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve and print the relative elapsed wall-clock time since an epoch.
|
||||
///
|
||||
/// The `Default` implementation for `Uptime` makes the epoch the current time.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct PrettyUptime {
|
||||
epoch: Instant,
|
||||
}
|
||||
|
||||
impl Default for PrettyUptime {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
epoch: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatTime for PrettyUptime {
|
||||
fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
|
||||
let e = self.epoch.elapsed();
|
||||
write!(w, "{:4}.{:2}s", e.as_secs(), e.subsec_millis())
|
||||
}
|
||||
}
|
||||
+37
-37
@@ -4,7 +4,7 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
mod assets;
|
||||
mod builder;
|
||||
mod build;
|
||||
mod bundle_utils;
|
||||
mod cli;
|
||||
mod config;
|
||||
@@ -13,65 +13,65 @@ mod dx_build_info;
|
||||
mod error;
|
||||
mod fastfs;
|
||||
mod filemap;
|
||||
mod logging;
|
||||
mod metadata;
|
||||
mod platform;
|
||||
mod profiles;
|
||||
mod rustup;
|
||||
mod serve;
|
||||
mod settings;
|
||||
mod slog;
|
||||
mod tooling;
|
||||
mod tracer;
|
||||
|
||||
pub(crate) use builder::*;
|
||||
pub(crate) use build::*;
|
||||
pub(crate) use cli::*;
|
||||
pub(crate) use config::*;
|
||||
pub(crate) use dioxus_crate::*;
|
||||
pub(crate) use dioxus_dx_wire_format::*;
|
||||
pub(crate) use error::*;
|
||||
pub(crate) use filemap::*;
|
||||
pub(crate) use logging::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use rustup::*;
|
||||
pub(crate) use settings::*;
|
||||
pub(crate) use tracer::*;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use Commands::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
async fn main() {
|
||||
// If we're being ran as a linker (likely from ourselves), we want to act as a linker instead.
|
||||
if let Some(link_action) = link::LinkAction::from_env() {
|
||||
return link_action.run();
|
||||
}
|
||||
|
||||
// Start the tracer so it captures logs from the build engine before we start the builder
|
||||
TraceController::initialize();
|
||||
let args = TraceController::initialize();
|
||||
let result = match args.action {
|
||||
Commands::Translate(opts) => opts.translate(),
|
||||
Commands::New(opts) => opts.create(),
|
||||
Commands::Init(opts) => opts.init(),
|
||||
Commands::Config(opts) => opts.config(),
|
||||
Commands::Autoformat(opts) => opts.autoformat(),
|
||||
Commands::Check(opts) => opts.check().await,
|
||||
Commands::Clean(opts) => opts.clean().await,
|
||||
Commands::Build(opts) => opts.run_cmd().await,
|
||||
Commands::Serve(opts) => opts.serve().await,
|
||||
Commands::Bundle(opts) => opts.bundle().await,
|
||||
Commands::Run(opts) => opts.run().await,
|
||||
Commands::Doctor(opts) => opts.run().await,
|
||||
};
|
||||
|
||||
match Cli::parse().action {
|
||||
Translate(opts) => opts
|
||||
.translate()
|
||||
.context("⛔️ Translation of HTML into RSX failed:"),
|
||||
// Provide a structured output for third party tools that can consume the output of the CLI
|
||||
match result {
|
||||
Ok(output) => {
|
||||
tracing::debug!(json = ?output);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
?err,
|
||||
json = ?StructuredOutput::Error {
|
||||
message: format!("{err:?}"),
|
||||
},
|
||||
);
|
||||
|
||||
New(opts) => opts.create().context("🚫 Creating new project failed:"),
|
||||
|
||||
Init(opts) => opts.init().context("🚫 Initializing a new project failed:"),
|
||||
|
||||
Config(opts) => opts.config().context("🚫 Configuring new project failed:"),
|
||||
|
||||
Autoformat(opts) => opts.autoformat().context("🚫 Error autoformatting RSX:"),
|
||||
|
||||
Check(opts) => opts.check().await.context("🚫 Error checking RSX:"),
|
||||
|
||||
Clean(opts) => opts.clean().context("🚫 Cleaning project failed:"),
|
||||
|
||||
Build(mut opts) => opts.build_it().await.context("🚫 Building project failed:"),
|
||||
|
||||
Serve(opts) => opts.serve().await.context("🚫 Serving project failed:"),
|
||||
|
||||
Bundle(opts) => opts.bundle().await.context("🚫 Bundling project failed:"),
|
||||
|
||||
Run(opts) => opts.run().await.context("🚫 Running project failed:"),
|
||||
|
||||
Doctor(opts) => opts.run().await.context("🚫 Checking project failed:"),
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -174,10 +174,7 @@ impl Platform {
|
||||
Some(Platform::Linux)
|
||||
}
|
||||
}
|
||||
"mobile" => {
|
||||
tracing::warn!("Could not autodetect mobile platform. Mobile platforms must be explicitly specified. Pass `--platform ios` or `--platform android` instead.");
|
||||
None
|
||||
}
|
||||
"mobile" => None,
|
||||
"liveview" => Some(Platform::Liveview),
|
||||
"server" => Some(Platform::Server),
|
||||
_ => None,
|
||||
|
||||
@@ -68,6 +68,7 @@ impl AppHandle {
|
||||
// These need to be stable within a release version (ie 0.6.0)
|
||||
let mut envs = vec![
|
||||
("DIOXUS_CLI_ENABLED", "true".to_string()),
|
||||
("RUST_BACKTRACE", "1".to_string()),
|
||||
(
|
||||
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
|
||||
devserver_ip.to_string(),
|
||||
@@ -116,7 +117,7 @@ impl AppHandle {
|
||||
|
||||
// https://developer.android.com/studio/run/emulator-commandline
|
||||
Platform::Android => {
|
||||
tracing::error!("Android is not yet supported, sorry!");
|
||||
self.open_android_sim(envs).await?;
|
||||
None
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ impl AppHandle {
|
||||
// we won't actually be using the build dir.
|
||||
let asset_dir = match self.runtime_asst_dir.as_ref() {
|
||||
Some(dir) => dir.to_path_buf().join("assets/"),
|
||||
None => self.app.asset_dir(),
|
||||
None => self.app.build.asset_dir(),
|
||||
};
|
||||
|
||||
tracing::debug!("Hotreloading asset {changed_file:?} in target {asset_dir:?}");
|
||||
@@ -237,13 +238,16 @@ impl AppHandle {
|
||||
/// TODO(jon): we should probably check if there's a simulator running before trying to install,
|
||||
/// and open the simulator if we have to.
|
||||
async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
|
||||
tracing::debug!("Installing app to simulator {:?}", self.app.app_dir());
|
||||
tracing::debug!(
|
||||
"Installing app to simulator {:?}",
|
||||
self.app.build.root_dir()
|
||||
);
|
||||
|
||||
let res = Command::new("xcrun")
|
||||
.arg("simctl")
|
||||
.arg("install")
|
||||
.arg("booted")
|
||||
.arg(self.app.app_dir())
|
||||
.arg(self.app.build.root_dir())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
@@ -262,15 +266,13 @@ impl AppHandle {
|
||||
.arg("launch")
|
||||
.arg("--console")
|
||||
.arg("booted")
|
||||
.arg("com.dioxuslabs")
|
||||
.arg(self.app.bundle_identifier())
|
||||
.envs(ios_envs)
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
tracing::debug!("Launched app on simulator with exit code: {child:?}");
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
@@ -309,7 +311,7 @@ impl AppHandle {
|
||||
// # xcrun devicectl device process resume --device "${DEVICE_UUID}" --pid "${STATUS_PID}" > "${XCRUN_DEVICE_PROCESS_RESUME_LOG_DIR}" 2>&1
|
||||
|
||||
use serde_json::Value;
|
||||
let app_path = self.app.app_dir();
|
||||
let app_path = self.app.build.root_dir();
|
||||
|
||||
install_app(&app_path).await?;
|
||||
|
||||
@@ -448,4 +450,32 @@ impl AppHandle {
|
||||
|
||||
unimplemented!("dioxus-cli doesn't support ios devices yet.")
|
||||
}
|
||||
|
||||
async fn open_android_sim(&self, envs: Vec<(&str, String)>) -> Result<()> {
|
||||
// Install
|
||||
// adb install -r app-debug.apk
|
||||
let _output = Command::new("adb")
|
||||
.arg("install")
|
||||
.arg("-r")
|
||||
.arg(self.app.apk_path())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
// adb shell am start -n com.example.androidfinal/com.example.androidfinal.MainActivity
|
||||
let _output = Command::new("adb")
|
||||
.arg("shell")
|
||||
.arg("am")
|
||||
.arg("start")
|
||||
.arg("-n")
|
||||
.arg("com.example.androidfinal/com.example.androidfinal.MainActivity")
|
||||
.envs(envs)
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
BuildUpdate, Builder, DioxusCrate, Error, Platform, Result, ServeArgs, TraceController,
|
||||
TraceSrc,
|
||||
};
|
||||
use crate::{BuildUpdate, Builder, Error, Platform, Result, ServeArgs, TraceController, TraceSrc};
|
||||
|
||||
mod ansi_buffer;
|
||||
mod detect;
|
||||
@@ -40,9 +37,13 @@ pub(crate) use watcher::*;
|
||||
/// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly.
|
||||
/// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server
|
||||
/// to a dynamic one on the fly.
|
||||
pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()> {
|
||||
pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
||||
// Redirect all logging the cli logger
|
||||
let mut tracer = TraceController::redirect();
|
||||
|
||||
// Load the krate and resolve the server args against it - this might log so do it after we turn on the tracer first
|
||||
let krate = args.load_krate()?;
|
||||
|
||||
// Note that starting the builder will queue up a build immediately
|
||||
let mut builder = Builder::start(&krate, args.build_args())?;
|
||||
let mut devserver = WebServer::start(&krate, &args)?;
|
||||
@@ -53,15 +54,15 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
|
||||
// This is our default splash screen. We might want to make this a fancier splash screen in the future
|
||||
// Also, these commands might not be the most important, but it's all we've got enabled right now
|
||||
tracing::info!(
|
||||
r#"Serving your Dioxus app: {} 🚀
|
||||
|
||||
- Press `ctrl+c` to exit the server
|
||||
- Press `r` to rebuild the app
|
||||
- Press `o` to open the app
|
||||
- Press `t` to toggle cargo output
|
||||
- Press `/` for more commands and shortcuts
|
||||
|
||||
Learn more at https://dioxuslabs.com/learn/0.6/getting_started"#,
|
||||
r#"-----------------------------------------------------------------
|
||||
Serving your Dioxus app: {} 🚀
|
||||
• Press `ctrl+c` to exit the server
|
||||
• Press `r` to rebuild the app
|
||||
• Press `o` to open the app
|
||||
• Press `v` to toggle verbose logging
|
||||
• Press `/` for more commands and shortcuts
|
||||
Learn more at https://dioxuslabs.com/learn/0.6/getting_started
|
||||
----------------------------------------------------------------"#,
|
||||
krate.executable_name()
|
||||
);
|
||||
|
||||
@@ -146,7 +147,7 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
|
||||
screen.new_build_update(&update);
|
||||
|
||||
// And then update the websocketed clients with the new build status in case they want it
|
||||
devserver.new_build_update(&update).await;
|
||||
devserver.new_build_update(&update, &builder).await;
|
||||
|
||||
// And then open the app if it's ready
|
||||
// todo: there might be more things to do here that require coordination with other pieces of the CLI
|
||||
@@ -213,6 +214,7 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
|
||||
// `Full rebuild:` to line up with
|
||||
// `Hotreloading:` to keep the alignment during long edit sessions
|
||||
tracing::info!("Full rebuild: triggered manually");
|
||||
|
||||
builder.rebuild(args.build_arguments.clone());
|
||||
runner.file_map.force_rebuild();
|
||||
devserver.start_build().await
|
||||
@@ -248,7 +250,6 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
|
||||
_ = devserver.shutdown().await;
|
||||
_ = screen.shutdown();
|
||||
builder.abort_all();
|
||||
tracer.shutdown();
|
||||
|
||||
if let Err(err) = err {
|
||||
eprintln!("Exiting with error: {}", err);
|
||||
|
||||
@@ -99,8 +99,8 @@ impl Output {
|
||||
more_modal_open: false,
|
||||
pending_logs: VecDeque::new(),
|
||||
throbber: RefCell::new(throbber_widgets_tui::ThrobberState::default()),
|
||||
trace: cfg.trace,
|
||||
verbose: cfg.verbose,
|
||||
trace: crate::logging::VERBOSITY.get().unwrap().trace,
|
||||
verbose: crate::logging::VERBOSITY.get().unwrap().verbose,
|
||||
tick_animation: false,
|
||||
tick_interval: {
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(TICK_RATE_MS));
|
||||
@@ -152,10 +152,8 @@ impl Output {
|
||||
.execute(DisableBracketedPaste)?;
|
||||
disable_raw_mode()?;
|
||||
|
||||
// print a few lines to not cut off the output
|
||||
for _ in 0..3 {
|
||||
println!();
|
||||
}
|
||||
// print a line to force the cursor down (no tearing)
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -331,10 +329,12 @@ impl Output {
|
||||
/// we won't be drawing it.
|
||||
pub(crate) fn new_build_update(&mut self, update: &BuildUpdate) {
|
||||
match update {
|
||||
BuildUpdate::CompilerMessage { .. } => {}
|
||||
BuildUpdate::Progress { .. } => self.tick_animation = true,
|
||||
BuildUpdate::Progress {
|
||||
stage: BuildStage::Starting { .. },
|
||||
} => self.tick_animation = true,
|
||||
BuildUpdate::BuildReady { .. } => self.tick_animation = false,
|
||||
BuildUpdate::BuildFailed { .. } => self.tick_animation = false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ impl Output {
|
||||
let mut area = frame.area();
|
||||
area.width = area.width.clamp(0, VIEWPORT_MAX_WIDTH);
|
||||
|
||||
let [_top, body, bottom] = Layout::vertical([
|
||||
let [_top, body, _bottom] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(1),
|
||||
@@ -391,7 +391,6 @@ impl Output {
|
||||
|
||||
self.render_borders(frame, area);
|
||||
self.render_body(frame, body, state);
|
||||
self.render_bottom_row(frame, bottom, state);
|
||||
self.render_body_title(frame, _top, state);
|
||||
}
|
||||
|
||||
@@ -512,6 +511,7 @@ impl Output {
|
||||
BuildStage::Failed => lines.push("Failed".red()),
|
||||
BuildStage::Aborted => lines.push("Aborted".red()),
|
||||
BuildStage::Restarting => lines.push("Restarting".yellow()),
|
||||
_ => {}
|
||||
};
|
||||
|
||||
frame.render_widget(Line::from(lines), status_line);
|
||||
@@ -712,23 +712,6 @@ impl Output {
|
||||
);
|
||||
}
|
||||
|
||||
/// Render the version number on the bottom right
|
||||
fn render_bottom_row(&self, frame: &mut Frame, area: Rect, _state: RenderState) {
|
||||
// Split the area into two chunks
|
||||
let row = Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area);
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
// "🧬 dx".dark_gray(),
|
||||
// " ".dark_gray(),
|
||||
// self.dx_version.as_str().dark_gray(),
|
||||
// " ".dark_gray(),
|
||||
]))
|
||||
.right_aligned(),
|
||||
row[1],
|
||||
);
|
||||
}
|
||||
|
||||
/// Render borders around the terminal, forcing an inner clear while we're at it
|
||||
fn render_borders(&self, frame: &mut Frame, area: Rect) {
|
||||
frame.render_widget(ratatui::widgets::Clear, area);
|
||||
@@ -784,7 +767,7 @@ impl Output {
|
||||
|
||||
// Render the log into an ansi string
|
||||
// We're going to add some metadata to it like the timestamp and source and then dump it to the raw ansi sequences we need to send to crossterm
|
||||
let output_sequence = tracemsg_to_ansi_string(log, term_size.width);
|
||||
let output_sequence = Self::tracemsg_to_ansi_string(log, term_size.width);
|
||||
|
||||
// Get the lines of the output sequence and their overflow
|
||||
let lines = output_sequence.lines().collect::<Vec<_>>();
|
||||
@@ -863,94 +846,94 @@ impl Output {
|
||||
false => VIEWPORT_HEIGHT_SMALL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String {
|
||||
let rendered = match log.content {
|
||||
TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(),
|
||||
TraceContent::Text(text) => text,
|
||||
};
|
||||
fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String {
|
||||
let rendered = match log.content {
|
||||
TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(),
|
||||
TraceContent::Text(text) => text,
|
||||
};
|
||||
|
||||
// Create a paragraph widget using the log line itself
|
||||
// From here on out, we want to work with the escaped ansi string and the "real lines" to be printed
|
||||
//
|
||||
// We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them
|
||||
// dark gray, just for readability.
|
||||
//
|
||||
// todo(jon): refactor this out to accept any widget, not just paragraphs
|
||||
let paragraph = Paragraph::new({
|
||||
use ansi_to_tui::IntoText;
|
||||
use chrono::Timelike;
|
||||
// Create a paragraph widget using the log line itself
|
||||
// From here on out, we want to work with the escaped ansi string and the "real lines" to be printed
|
||||
//
|
||||
// We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them
|
||||
// dark gray, just for readability.
|
||||
//
|
||||
// todo(jon): refactor this out to accept any widget, not just paragraphs
|
||||
let paragraph = Paragraph::new({
|
||||
use ansi_to_tui::IntoText;
|
||||
use chrono::Timelike;
|
||||
|
||||
let mut text = Text::default();
|
||||
let mut text = Text::default();
|
||||
|
||||
for (idx, raw_line) in rendered.lines().enumerate() {
|
||||
let line_as_text = raw_line.into_text().unwrap();
|
||||
let is_pretending_to_be_frame = raw_line
|
||||
.chars()
|
||||
.all(|c| c == '=' || c == '-' || c == ' ' || c == '─');
|
||||
for (idx, raw_line) in rendered.lines().enumerate() {
|
||||
let line_as_text = raw_line.into_text().unwrap();
|
||||
let is_pretending_to_be_frame = raw_line
|
||||
.chars()
|
||||
.all(|c| c == '=' || c == '-' || c == ' ' || c == '─');
|
||||
|
||||
for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() {
|
||||
let mut out_line = if idx == 0 && subline_idx == 0 {
|
||||
let mut formatted_line = Line::default();
|
||||
for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() {
|
||||
let mut out_line = if idx == 0 && subline_idx == 0 {
|
||||
let mut formatted_line = Line::default();
|
||||
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"{:02}:{:02}:{:02} ",
|
||||
log.timestamp.hour(),
|
||||
log.timestamp.minute(),
|
||||
log.timestamp.second()
|
||||
))
|
||||
.dark_gray(),
|
||||
);
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"[{src}] {padding}",
|
||||
src = log.source,
|
||||
padding =
|
||||
" ".repeat(3usize.saturating_sub(log.source.to_string().len()))
|
||||
))
|
||||
.style(match log.source {
|
||||
TraceSrc::App(_platform) => Style::new().blue(),
|
||||
TraceSrc::Dev => Style::new().magenta(),
|
||||
TraceSrc::Build => Style::new().yellow(),
|
||||
TraceSrc::Bundle => Style::new().magenta(),
|
||||
TraceSrc::Cargo => Style::new().yellow(),
|
||||
TraceSrc::Unknown => Style::new().gray(),
|
||||
}),
|
||||
);
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"{:02}:{:02}:{:02} ",
|
||||
log.timestamp.hour(),
|
||||
log.timestamp.minute(),
|
||||
log.timestamp.second()
|
||||
))
|
||||
.dark_gray(),
|
||||
);
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"[{src}] {padding}",
|
||||
src = log.source,
|
||||
padding =
|
||||
" ".repeat(3usize.saturating_sub(log.source.to_string().len()))
|
||||
))
|
||||
.style(match log.source {
|
||||
TraceSrc::App(_platform) => Style::new().blue(),
|
||||
TraceSrc::Dev => Style::new().magenta(),
|
||||
TraceSrc::Build => Style::new().yellow(),
|
||||
TraceSrc::Bundle => Style::new().magenta(),
|
||||
TraceSrc::Cargo => Style::new().yellow(),
|
||||
TraceSrc::Unknown => Style::new().gray(),
|
||||
}),
|
||||
);
|
||||
|
||||
for span in line.spans {
|
||||
formatted_line.push_span(span);
|
||||
for span in line.spans {
|
||||
formatted_line.push_span(span);
|
||||
}
|
||||
|
||||
formatted_line
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
if is_pretending_to_be_frame {
|
||||
out_line = out_line.dark_gray();
|
||||
}
|
||||
|
||||
formatted_line
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
if is_pretending_to_be_frame {
|
||||
out_line = out_line.dark_gray();
|
||||
text.lines.push(out_line);
|
||||
}
|
||||
|
||||
text.lines.push(out_line);
|
||||
}
|
||||
}
|
||||
|
||||
text
|
||||
});
|
||||
text
|
||||
});
|
||||
|
||||
// We want to get the escaped ansii string and then by dumping the paragraph as ascii codes (again)
|
||||
//
|
||||
// This is important because the line_count method on paragraph takes into account the width of these codes
|
||||
// the 3000 clip width is to bound log lines to a reasonable memory usage
|
||||
// We could consider reusing this buffer since it's a lot to allocate, but log printing is not the
|
||||
// slowest thing in the world and allocating is pretty fast...
|
||||
//
|
||||
// After we've dumped the ascii out, we want to call "trim_end" which ensures we don't attempt
|
||||
// to print extra characters as lines, since AnsiStringBuffer will in fact attempt to print empty
|
||||
// cells as characters. That might not actually be important, but we want to shrink the buffer
|
||||
// before printing it
|
||||
let line_count = paragraph.line_count(term_width);
|
||||
AnsiStringBuffer::new(3000, line_count as u16).render(¶graph)
|
||||
// We want to get the escaped ansii string and then by dumping the paragraph as ascii codes (again)
|
||||
//
|
||||
// This is important because the line_count method on paragraph takes into account the width of these codes
|
||||
// the 3000 clip width is to bound log lines to a reasonable memory usage
|
||||
// We could consider reusing this buffer since it's a lot to allocate, but log printing is not the
|
||||
// slowest thing in the world and allocating is pretty fast...
|
||||
//
|
||||
// After we've dumped the ascii out, we want to call "trim_end" which ensures we don't attempt
|
||||
// to print extra characters as lines, since AnsiStringBuffer will in fact attempt to print empty
|
||||
// cells as characters. That might not actually be important, but we want to shrink the buffer
|
||||
// before printing it
|
||||
let line_count = paragraph.line_count(term_width);
|
||||
AnsiStringBuffer::new(3000, line_count as u16).render(¶graph)
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,16 @@ impl AppRunner {
|
||||
// todo(jon): we should allow rebinding to the same port in fullstack itself
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
// Add some cute logging
|
||||
if self.builds_opened == 0 {
|
||||
tracing::info!(
|
||||
"Build completed successfully in {:?}ms, launching app! 💫",
|
||||
app.app.time_taken.as_millis()
|
||||
);
|
||||
} else {
|
||||
tracing::info!("Build completed in {:?}ms", app.app.time_taken.as_millis());
|
||||
}
|
||||
|
||||
// Start the new app before we kill the old one to give it a little bit of time
|
||||
let mut handle = AppHandle::new(app).await?;
|
||||
handle
|
||||
@@ -120,6 +130,7 @@ impl AppRunner {
|
||||
self.builds_opened == 0 && should_open_web,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.builds_opened += 1;
|
||||
self.running.insert(platform, handle);
|
||||
|
||||
@@ -264,17 +275,17 @@ impl AppRunner {
|
||||
.arg("simctl")
|
||||
.arg("get_app_container")
|
||||
.arg("booted")
|
||||
.arg("com.dioxuslabs")
|
||||
.arg(runner.app.bundle_identifier())
|
||||
.output()
|
||||
.await;
|
||||
|
||||
if let Ok(res) = res {
|
||||
tracing::debug!("Using runtime asset dir: {:?}", res);
|
||||
tracing::trace!("Using runtime asset dir: {:?}", res);
|
||||
|
||||
if let Ok(out) = String::from_utf8(res.stdout) {
|
||||
let out = out.trim();
|
||||
|
||||
tracing::debug!("Setting Runtime asset dir: {out:?}");
|
||||
tracing::trace!("Setting Runtime asset dir: {out:?}");
|
||||
runner.runtime_asst_dir = Some(PathBuf::from(out));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,11 @@ impl WebServer {
|
||||
}
|
||||
|
||||
/// Sends an updated build status to all clients.
|
||||
pub(crate) async fn new_build_update(&mut self, update: &BuildUpdate) {
|
||||
pub(crate) async fn new_build_update(
|
||||
&mut self,
|
||||
update: &BuildUpdate,
|
||||
builder: &super::Builder,
|
||||
) {
|
||||
match update {
|
||||
BuildUpdate::Progress { stage } => {
|
||||
// Todo(miles): wire up more messages into the splash screen UI
|
||||
@@ -220,11 +224,13 @@ impl WebServer {
|
||||
krate,
|
||||
..
|
||||
} => {
|
||||
self.build_status.set(Status::Building {
|
||||
progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),
|
||||
build_message: format!("{krate} compiling"),
|
||||
});
|
||||
self.send_build_status().await;
|
||||
if !builder.is_finished() {
|
||||
self.build_status.set(Status::Building {
|
||||
progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),
|
||||
build_message: format!("{krate} compiling"),
|
||||
});
|
||||
self.send_build_status().await;
|
||||
}
|
||||
}
|
||||
BuildStage::OptimizingWasm {} => {}
|
||||
BuildStage::Aborted => {}
|
||||
@@ -250,7 +256,7 @@ impl WebServer {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::debug!("Sending hotreload to clients {:?}", reload);
|
||||
tracing::trace!("Sending hotreload to clients {:?}", reload);
|
||||
|
||||
let msg = DevserverMsg::HotReload(reload);
|
||||
let msg = serde_json::to_string(&msg).unwrap();
|
||||
@@ -281,6 +287,8 @@ impl WebServer {
|
||||
|
||||
/// Tells all clients to reload if possible for new changes.
|
||||
pub(crate) async fn send_reload_command(&mut self) {
|
||||
tracing::trace!("Sending reload to toast");
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
self.build_status.set(Status::Ready);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in new issue
Block a user