final 0.7 fixes: xcode 15/16 hybrid, auto openssldir for android, include_*! tracking hotpatch, fix /assets/ brick at high opt levels, windows linker file, etc (#4376)
* wip: openssl autodetection * add voltaile read inside of asset fn * support xcode 15 for --device * vendor openssl on android * Smaller width TUI * switch to anyhow for the entire CLI * add backtrace and print double printing of cargo_metadata errors * add some padding to multi-line anyhow error * check semvers * don't open if not interactive * set the tailwind current_dir so it doesn't twitch * get the oboe demo working * copy the c++ library on android * cleanup env vars * add depinfo watcher * fix stale hotpatches and crate count * add musl support * remove outdated autofmt test * update scanner app * add hotdog app and bundle to ci * upload dx on every branch * collapse mobile into desktop * `dx doctor` command * windows command files * no pipe on doctor * fix ci? * fix links * fix ci * use main instead of nightly * bump node * build cli for host * cargo doc * prep dog_app for bundle * package hotdog * no bundle or nightly cli * comment out nightly cli * add quotes to linker files
This commit is contained in:
1 parent
97947b169b
commit
727752a965
78 files changed
+2270
-817
No files matched your search
@@ -149,9 +149,7 @@ built = { version = "0.7.5", features = ["git2"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
plugin = []
|
||||
tokio-console = ["dep:console-subscriber", "tokio/tracing"]
|
||||
bundle = []
|
||||
no-downloads = []
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -34,7 +34,7 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use const_serialize::{ConstVec, SerializeConst};
|
||||
use dioxus_cli_opt::AssetManifest;
|
||||
use manganis::BundledAsset;
|
||||
@@ -352,11 +352,10 @@ pub(crate) fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<AssetMa
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
bail!(
|
||||
"Failed to re-sign the binary with codesign after finalizing the assets: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
)
|
||||
.into());
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform,
|
||||
ProgressRx, ProgressTx, Result, StructuredOutput,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use dioxus_cli_opt::process_file_to;
|
||||
use futures_util::{future::OptionFuture, pin_mut, FutureExt};
|
||||
use itertools::Itertools;
|
||||
@@ -122,25 +122,13 @@ impl AppBuilder {
|
||||
/// updates (e.g., `wait`, `finish_build`).
|
||||
/// - The build process is designed to be cancellable and restartable using methods like `abort_all`
|
||||
/// or `rebuild`.
|
||||
pub(crate) fn start(request: &BuildRequest, mode: BuildMode) -> Result<Self> {
|
||||
pub(crate) fn new(request: &BuildRequest) -> Result<Self> {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
|
||||
Ok(Self {
|
||||
build: request.clone(),
|
||||
stage: BuildStage::Initializing,
|
||||
build_task: tokio::spawn({
|
||||
let request = request.clone();
|
||||
let tx = tx.clone();
|
||||
async move {
|
||||
let ctx = BuildContext {
|
||||
mode,
|
||||
tx: tx.clone(),
|
||||
};
|
||||
request.verify_tooling(&ctx).await?;
|
||||
request.prepare_build_dir()?;
|
||||
request.build(&ctx).await
|
||||
}
|
||||
}),
|
||||
build_task: tokio::task::spawn(std::future::pending()),
|
||||
tx,
|
||||
rx,
|
||||
patches: vec![],
|
||||
@@ -164,6 +152,29 @@ impl AppBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `AppBuilder` and immediately start a build process.
|
||||
pub fn started(request: &BuildRequest, mode: BuildMode) -> Result<Self> {
|
||||
let mut builder = Self::new(request)?;
|
||||
builder.start(mode);
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
pub(crate) fn start(&mut self, mode: BuildMode) {
|
||||
self.build_task = tokio::spawn({
|
||||
let request = self.build.clone();
|
||||
let tx = self.tx.clone();
|
||||
async move {
|
||||
let ctx = BuildContext {
|
||||
mode,
|
||||
tx: tx.clone(),
|
||||
};
|
||||
request.verify_tooling(&ctx).await?;
|
||||
request.prepare_build_dir()?;
|
||||
request.build(&ctx).await
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Wait for any new updates to the builder - either it completed or gave us a message etc
|
||||
pub(crate) async fn wait(&mut self) -> BuilderUpdate {
|
||||
use futures_util::StreamExt;
|
||||
@@ -178,7 +189,7 @@ impl AppBuilder {
|
||||
match bundle {
|
||||
Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle },
|
||||
Ok(Err(err)) => BuilderUpdate::BuildFailed { err },
|
||||
Err(err) => BuilderUpdate::BuildFailed { err: crate::Error::Runtime(format!("Build panicked! {:#?}", err)) },
|
||||
Err(err) => BuilderUpdate::BuildFailed { err: anyhow::anyhow!("Build panicked! {:#?}", err) },
|
||||
}
|
||||
},
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => {
|
||||
@@ -596,8 +607,12 @@ impl AppBuilder {
|
||||
let triple = self.build.triple.clone();
|
||||
let asset_dir = self.build.asset_dir();
|
||||
|
||||
// Hotpatch asset!() calls
|
||||
for bundled in res.assets.assets() {
|
||||
let original_artifacts = self.artifacts.as_mut().unwrap();
|
||||
let original_artifacts = self
|
||||
.artifacts
|
||||
.as_mut()
|
||||
.context("No artifacts to hotpatch")?;
|
||||
|
||||
if original_artifacts.assets.contains(bundled) {
|
||||
continue;
|
||||
@@ -623,6 +638,18 @@ impl AppBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to add `include!()` calls to the watcher so we can watch changes as they evolve
|
||||
for file in res.depinfo.files.iter() {
|
||||
let original_artifacts = self
|
||||
.artifacts
|
||||
.as_mut()
|
||||
.context("No artifacts to hotpatch")?;
|
||||
|
||||
if !original_artifacts.depinfo.files.contains(file) {
|
||||
original_artifacts.depinfo.files.push(file.clone());
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("Patching {} -> {}", original.display(), new.display());
|
||||
|
||||
let mut jump_table = crate::build::create_jump_table(&new, &triple, cache)?;
|
||||
@@ -891,7 +918,7 @@ impl AppBuilder {
|
||||
.context("Failed to parse xcrun output")?;
|
||||
let device_uuid = json["result"]["devices"][0]["identifier"]
|
||||
.as_str()
|
||||
.ok_or("Failed to extract device UUID")?
|
||||
.context("Failed to extract device UUID")?
|
||||
.to_string();
|
||||
|
||||
Ok(device_uuid)
|
||||
@@ -918,14 +945,14 @@ impl AppBuilder {
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!("Failed to install app: {:?}", output).into());
|
||||
bail!("Failed to install app: {:?}", output);
|
||||
}
|
||||
|
||||
let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)
|
||||
.context("Failed to parse xcrun output")?;
|
||||
let installation_url = json["result"]["installedApplications"][0]["installationURL"]
|
||||
.as_str()
|
||||
.ok_or("Failed to extract installation URL")?
|
||||
.context("Failed to extract installation URL from xcrun output")?
|
||||
.to_string();
|
||||
|
||||
Ok(installation_url)
|
||||
@@ -953,7 +980,7 @@ impl AppBuilder {
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!("Failed to launch app: {:?}", output).into());
|
||||
bail!("Failed to launch app: {:?}", output);
|
||||
}
|
||||
|
||||
let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)
|
||||
@@ -961,7 +988,7 @@ impl AppBuilder {
|
||||
|
||||
let status_pid = json["result"]["process"]["processIdentifier"]
|
||||
.as_u64()
|
||||
.ok_or("Failed to extract process identifier")?;
|
||||
.context("Failed to extract process identifier")?;
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args([
|
||||
@@ -978,7 +1005,7 @@ impl AppBuilder {
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!("Failed to resume app: {:?}", output).into());
|
||||
bail!("Failed to resume app: {:?}", output);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -998,18 +1025,27 @@ To accept the agreement, go to https://developer.apple.com/account
|
||||
To create a provisioning profile, follow the instructions here:
|
||||
https://developer.apple.com/documentation/xcode/sharing-your-teams-signing-certificates"#;
|
||||
|
||||
let profiles_folder = dirs::home_dir()
|
||||
// Check the xcode 16 location first
|
||||
let mut profiles_folder = dirs::home_dir()
|
||||
.context("Your machine has no home-dir")?
|
||||
.join("Library/Developer/Xcode/UserData/Provisioning Profiles");
|
||||
|
||||
// If it doesn't exist, check the old location
|
||||
if !profiles_folder.exists() {
|
||||
profiles_folder = dirs::home_dir()
|
||||
.context("Your machine has no home-dir")?
|
||||
.join("Library/MobileDevice/Provisioning Profiles");
|
||||
}
|
||||
|
||||
if !profiles_folder.exists() || profiles_folder.read_dir()?.next().is_none() {
|
||||
tracing::error!(
|
||||
r#"No provisioning profiles found when trying to codesign the app.
|
||||
We checked the folder: {}
|
||||
We checked the folders:
|
||||
- XCode16: ~/Library/Developer/Xcode/UserData/Provisioning Profiles
|
||||
- XCode15: ~/Library/MobileDevice/Provisioning Profiles
|
||||
|
||||
{CODESIGN_ERROR}
|
||||
"#,
|
||||
profiles_folder.display()
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1125,8 +1161,10 @@ We checked the folder: {}
|
||||
.context("Failed to codesign the app")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
|
||||
return Err(format!("Failed to codesign the app: {stderr}").into());
|
||||
bail!(
|
||||
"Failed to codesign the app: {}",
|
||||
String::from_utf8(output.stderr).unwrap_or_default()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -105,7 +105,7 @@ impl BuildContext {
|
||||
}
|
||||
|
||||
pub(crate) fn status_build_error(&self, line: String) {
|
||||
tracing::error!(dx_src = ?TraceSrc::Cargo, "{line}");
|
||||
tracing::debug!(dx_src = ?TraceSrc::Cargo, "{line}");
|
||||
}
|
||||
|
||||
pub(crate) fn status_build_message(&self, line: String) {
|
||||
|
||||
+356
-128
@@ -320,8 +320,9 @@ use crate::{
|
||||
RustcArgs, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig, Workspace,
|
||||
DX_RUSTC_WRAPPER_ENV_VAR,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use cargo_metadata::diagnostic::Diagnostic;
|
||||
use depinfo::RustcDepInfo;
|
||||
use dioxus_cli_config::format_base_path_meta_element;
|
||||
use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV};
|
||||
use dioxus_cli_opt::{process_file_to, AssetManifest};
|
||||
@@ -331,7 +332,7 @@ use manganis::AssetOptions;
|
||||
use manganis_core::AssetVariant;
|
||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, ffi::OsString};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
io::Write,
|
||||
@@ -391,6 +392,7 @@ pub(crate) struct BuildRequest {
|
||||
pub(crate) link_args_file: Arc<NamedTempFile>,
|
||||
pub(crate) link_err_file: Arc<NamedTempFile>,
|
||||
pub(crate) rustc_wrapper_args_file: Arc<NamedTempFile>,
|
||||
pub(crate) command_file: Arc<NamedTempFile>,
|
||||
pub(crate) base_path: Option<String>,
|
||||
pub(crate) using_dioxus_explicitly: bool,
|
||||
}
|
||||
@@ -443,6 +445,7 @@ pub struct BuildArtifacts {
|
||||
pub(crate) assets: AssetManifest,
|
||||
pub(crate) mode: BuildMode,
|
||||
pub(crate) patch_cache: Option<Arc<HotpatchModuleCache>>,
|
||||
pub(crate) depinfo: RustcDepInfo,
|
||||
}
|
||||
|
||||
impl BuildRequest {
|
||||
@@ -569,13 +572,10 @@ impl BuildRequest {
|
||||
},
|
||||
None if !using_dioxus_explicitly => Platform::autodetect_from_cargo_feature("desktop").unwrap(),
|
||||
None => match enabled_platforms.len() {
|
||||
0 => return Err(anyhow::anyhow!("No platform specified and no platform marked as default in Cargo.toml. Try specifying a platform with `--platform`").into()),
|
||||
0 => bail!("No platform specified and no platform marked as default in Cargo.toml. Try specifying a platform with `--platform`"),
|
||||
1 => enabled_platforms[0],
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Multiple platforms enabled in Cargo.toml. Please specify a platform with `--platform` or set a default platform in Cargo.toml"
|
||||
)
|
||||
.into())
|
||||
bail!("Multiple platforms enabled in Cargo.toml. Please specify a platform with `--platform` or set a default platform in Cargo.toml")
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -662,7 +662,13 @@ impl BuildRequest {
|
||||
"-Clink-arg=-landroid".to_string(),
|
||||
"-Clink-arg=-llog".to_string(),
|
||||
"-Clink-arg=-lOpenSLES".to_string(),
|
||||
"-Clink-arg=-lc++".to_string(),
|
||||
"-Clink-arg=-lc++abi".to_string(),
|
||||
"-Clink-arg=-Wl,--export-dynamic".to_string(),
|
||||
format!(
|
||||
"-Clink-arg=-Wl,--sysroot={}",
|
||||
workspace.android_tools()?.sysroot().display()
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -712,6 +718,9 @@ impl BuildRequest {
|
||||
let session_cache_dir = Arc::new(
|
||||
TempDir::new().context("Failed to create temporary directory for session cache")?,
|
||||
);
|
||||
let command_file = Arc::new(
|
||||
NamedTempFile::new().context("Failed to create temporary file for linker args")?,
|
||||
);
|
||||
|
||||
let extra_rustc_args = shell_words::split(&args.rustc_args.clone().unwrap_or_default())
|
||||
.context("Failed to parse rustc args")?;
|
||||
@@ -752,6 +761,7 @@ impl BuildRequest {
|
||||
custom_linker,
|
||||
link_args_file,
|
||||
link_err_file,
|
||||
command_file,
|
||||
session_cache_dir,
|
||||
rustc_wrapper_args_file,
|
||||
extra_rustc_args,
|
||||
@@ -774,6 +784,9 @@ impl BuildRequest {
|
||||
// We need to make sure to not react to this though, so the filemap must cache it
|
||||
_ = self.bust_fingerprint(ctx);
|
||||
|
||||
// Run any pre-build steps like tailwind, etc
|
||||
self.prebuild().await?;
|
||||
|
||||
// Run the cargo build to produce our artifacts
|
||||
let mut artifacts = self.cargo_build(ctx).await?;
|
||||
|
||||
@@ -793,12 +806,23 @@ impl BuildRequest {
|
||||
ctx.status_start_bundle();
|
||||
|
||||
self.write_executable(ctx, &artifacts.exe, &mut artifacts.assets)
|
||||
.await?;
|
||||
self.write_frameworks(ctx, &artifacts.direct_rustc).await?;
|
||||
self.write_assets(ctx, &artifacts.assets).await?;
|
||||
self.write_metadata().await?;
|
||||
self.optimize(ctx).await?;
|
||||
self.assemble(ctx).await?;
|
||||
.await
|
||||
.context("Failed to write executable")?;
|
||||
self.write_frameworks(ctx, &artifacts.direct_rustc)
|
||||
.await
|
||||
.context("Failed to write frameworks")?;
|
||||
self.write_assets(ctx, &artifacts.assets)
|
||||
.await
|
||||
.context("Failed to write assets")?;
|
||||
self.write_metadata()
|
||||
.await
|
||||
.context("Failed to write metadata")?;
|
||||
self.optimize(ctx)
|
||||
.await
|
||||
.context("Failed to optimize build")?;
|
||||
self.assemble(ctx)
|
||||
.await
|
||||
.context("Failed to assemble build")?;
|
||||
|
||||
tracing::debug!("Bundle created at {}", self.root_dir().display());
|
||||
}
|
||||
@@ -916,10 +940,7 @@ impl BuildRequest {
|
||||
// since that is a really bad user experience.
|
||||
Message::BuildFinished(finished) => {
|
||||
if !finished.success {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Cargo build failed, signaled by the compiler. Toggle tracing mode (press `t`) for more information."
|
||||
)
|
||||
.into());
|
||||
bail!("cargo build finished with errors.")
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -971,6 +992,7 @@ impl BuildRequest {
|
||||
let time_end = SystemTime::now();
|
||||
let mode = ctx.mode.clone();
|
||||
let platform = self.platform;
|
||||
let depinfo = RustcDepInfo::from_file(&exe.with_extension("d")).unwrap_or_default();
|
||||
|
||||
tracing::debug!(
|
||||
"Build completed successfully in {}us: {:?}",
|
||||
@@ -986,6 +1008,7 @@ impl BuildRequest {
|
||||
time_start,
|
||||
assets,
|
||||
mode,
|
||||
depinfo,
|
||||
patch_cache: None,
|
||||
})
|
||||
}
|
||||
@@ -1079,6 +1102,7 @@ impl BuildRequest {
|
||||
// todo - how do we handle windows dlls? we don't want to bundle the system dlls
|
||||
// for now, we don't do anything with dlls, and only use .dylibs and .so files
|
||||
|
||||
// Write dylibs and dlls to the frameworks folder
|
||||
if arg.ends_with(".dylib") | arg.ends_with(".so") {
|
||||
let from = PathBuf::from(arg);
|
||||
let to = framework_dir.join(from.file_name().unwrap());
|
||||
@@ -1104,6 +1128,16 @@ impl BuildRequest {
|
||||
std::fs::copy(from, to)?;
|
||||
}
|
||||
}
|
||||
|
||||
// On android, the c++_static flag means we need to copy the libc++_shared.so precompiled
|
||||
// library to the jniLibs folder
|
||||
if arg.contains("-lc++_static") && self.platform == Platform::Android {
|
||||
std::fs::copy(
|
||||
self.workspace.android_tools()?.libcpp_shared(&self.triple),
|
||||
framework_dir.join("libc++_shared.so"),
|
||||
)
|
||||
.with_context(|| "Failed to copy libc++_shared.so into bundle")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1115,6 +1149,13 @@ impl BuildRequest {
|
||||
self.root_dir().join("Contents").join("Frameworks")
|
||||
}
|
||||
OperatingSystem::IOS(_) => self.root_dir().join("Frameworks"),
|
||||
OperatingSystem::Linux if self.platform == Platform::Android => self
|
||||
.root_dir()
|
||||
.join("app")
|
||||
.join("src")
|
||||
.join("main")
|
||||
.join("jniLibs")
|
||||
.join("arm64-v8a"),
|
||||
OperatingSystem::Linux | OperatingSystem::Windows => self.root_dir(),
|
||||
_ => self.root_dir(),
|
||||
}
|
||||
@@ -1129,14 +1170,6 @@ impl BuildRequest {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Run the tailwind build before bundling anything else
|
||||
crate::TailwindCli::run_once(
|
||||
self.package_manifest_dir(),
|
||||
self.config.application.tailwind_input.clone(),
|
||||
self.config.application.tailwind_output.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let asset_dir = self.asset_dir();
|
||||
|
||||
// First, clear the asset dir of any files that don't exist in the new manifest
|
||||
@@ -1379,15 +1412,28 @@ impl BuildRequest {
|
||||
|
||||
tracing::trace!("Linking with {:?} using args: {:#?}", linker, object_files);
|
||||
|
||||
let mut out_args: Vec<OsString> = vec![];
|
||||
out_args.extend(object_files.iter().map(Into::into));
|
||||
out_args.extend(dylibs.iter().map(Into::into));
|
||||
out_args.extend(self.thin_link_args(&args)?.iter().map(Into::into));
|
||||
out_args.extend(out_arg.iter().map(Into::into));
|
||||
|
||||
if cfg!(windows) {
|
||||
let cmd_contents: String = out_args
|
||||
.iter()
|
||||
.map(|s| format!("\"{}\"", s.to_string_lossy()))
|
||||
.join(" ");
|
||||
std::fs::write(self.command_file.path(), cmd_contents)
|
||||
.context("Failed to write linker command file")?;
|
||||
out_args = vec![format!("@{}", self.command_file.path().display()).into()];
|
||||
}
|
||||
|
||||
// Run the linker directly!
|
||||
//
|
||||
// We dump its output directly into the patch exe location which is different than how rustc
|
||||
// does it since it uses llvm-objcopy into the `target/debug/` folder.
|
||||
let res = Command::new(linker)
|
||||
.args(object_files.iter())
|
||||
.args(dylibs.iter())
|
||||
.args(self.thin_link_args(&args)?)
|
||||
.args(out_arg)
|
||||
.args(out_args)
|
||||
.env_clear()
|
||||
.envs(rustc_args.envs.iter().map(|(k, v)| (k, v)))
|
||||
.output()
|
||||
@@ -1561,7 +1607,7 @@ impl BuildRequest {
|
||||
}
|
||||
|
||||
LinkerFlavor::Unsupported => {
|
||||
return Err(anyhow::anyhow!("Unsupported platform for thin linking").into())
|
||||
bail!("Unsupported platform for thin linking")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1791,7 +1837,7 @@ impl BuildRequest {
|
||||
// And then remove the rest of the rlibs
|
||||
//
|
||||
// We also need to insert the -force_load flag to force the linker to load the archive
|
||||
let mut args = rustc_args.link_args.clone();
|
||||
let mut args: Vec<_> = rustc_args.link_args.iter().skip(1).cloned().collect();
|
||||
if let Some(last_object) = args.iter().rposition(|arg| arg.ends_with(".o")) {
|
||||
if archive_has_contents {
|
||||
match self.linker_flavor() {
|
||||
@@ -1879,21 +1925,33 @@ impl BuildRequest {
|
||||
args.remove(flavor_idx);
|
||||
}
|
||||
|
||||
// Set the output file
|
||||
match self.triple.operating_system {
|
||||
OperatingSystem::Windows => args.push(format!("/OUT:{}", exe.display())),
|
||||
_ => args.extend(["-o".to_string(), exe.display().to_string()]),
|
||||
}
|
||||
|
||||
// And now we can run the linker with our new args
|
||||
let linker = self.select_linker()?;
|
||||
|
||||
tracing::trace!("Fat linking with args: {:?} {:#?}", linker, args);
|
||||
tracing::trace!("Fat linking with env: {:#?}", rustc_args.envs);
|
||||
tracing::trace!("Fat linking with env:");
|
||||
for e in rustc_args.envs.iter() {
|
||||
tracing::trace!(" {}={}", e.0, e.1);
|
||||
}
|
||||
|
||||
// Handle windows command files
|
||||
let mut out_args = args.clone();
|
||||
if cfg!(windows) {
|
||||
let cmd_contents: String = out_args.iter().map(|f| format!("\"{f}\"")).join(" ");
|
||||
std::fs::write(self.command_file.path(), cmd_contents)
|
||||
.context("Failed to write linker command file")?;
|
||||
out_args = vec![format!("@{}", self.command_file.path().display())];
|
||||
}
|
||||
|
||||
// Run the linker directly!
|
||||
let out_arg = match self.triple.operating_system {
|
||||
OperatingSystem::Windows => vec![format!("/OUT:{}", exe.display())],
|
||||
_ => vec!["-o".to_string(), exe.display().to_string()],
|
||||
};
|
||||
|
||||
let res = Command::new(linker)
|
||||
.args(args.iter().skip(1))
|
||||
.args(out_arg)
|
||||
.args(out_args)
|
||||
.env_clear()
|
||||
.envs(rustc_args.envs.iter().map(|(k, v)| (k, v)))
|
||||
.output()
|
||||
@@ -2062,12 +2120,8 @@ impl BuildRequest {
|
||||
cmd.arg("-Crelocation-model=pic");
|
||||
}
|
||||
|
||||
tracing::debug!("Direct rustc: {:#?}", cmd);
|
||||
|
||||
cmd.envs(rustc_args.envs.iter().cloned());
|
||||
|
||||
// tracing::trace!("Setting env vars: {:#?}", rustc_args.envs);
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
@@ -2084,16 +2138,24 @@ impl BuildRequest {
|
||||
_ => {
|
||||
let mut cmd = Command::new("cargo");
|
||||
|
||||
let env = self.cargo_build_env_vars(ctx)?;
|
||||
let args = self.cargo_build_arguments(ctx);
|
||||
|
||||
tracing::trace!("Building with cargo rustc");
|
||||
for e in env.iter() {
|
||||
tracing::trace!(": {}={}", e.0, e.1);
|
||||
}
|
||||
|
||||
for a in args.iter() {
|
||||
tracing::trace!(": {}", a);
|
||||
}
|
||||
|
||||
cmd.arg("rustc")
|
||||
.current_dir(self.crate_dir())
|
||||
.arg("--message-format")
|
||||
.arg("json-diagnostic-rendered-ansi")
|
||||
.args(self.cargo_build_arguments(ctx))
|
||||
.envs(
|
||||
self.cargo_build_env_vars(ctx)?
|
||||
.iter()
|
||||
.map(|(k, v)| (k.as_ref(), v)),
|
||||
);
|
||||
.args(args)
|
||||
.envs(env.iter().map(|(k, v)| (k.as_ref(), v)));
|
||||
|
||||
if ctx.mode == BuildMode::Fat {
|
||||
cmd.env(
|
||||
@@ -2109,8 +2171,6 @@ impl BuildRequest {
|
||||
);
|
||||
}
|
||||
|
||||
tracing::debug!("Cargo: {:#?}", cmd);
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
}
|
||||
@@ -2351,7 +2411,67 @@ impl BuildRequest {
|
||||
Ok(env_vars)
|
||||
}
|
||||
|
||||
/// Set the environment variables required for building on Android.
|
||||
///
|
||||
/// This involves setting sysroots, CC, CXX, AR, and other environment variables along with
|
||||
/// vars that cc-rs uses for its C/C++ compilation.
|
||||
///
|
||||
/// We pulled the environment setup from `cargo ndk` and attempt to mimic its behavior to retain
|
||||
/// compatibility with existing crates that work with `cargo ndk`.
|
||||
///
|
||||
/// <https://github.com/bbqsrc/cargo-ndk/blob/1d1a6dc70a99b7f95bc71ed07bf893ef37966efc/src/cargo.rs#L97-L102>
|
||||
///
|
||||
/// cargo-ndk is MIT licensed.
|
||||
///
|
||||
/// <https://github.com/bbqsrc/cargo-ndk>
|
||||
fn android_env_vars(&self) -> Result<Vec<(Cow<'static, str>, String)>> {
|
||||
// Derived from getenv_with_target_prefixes in `cc` crate.
|
||||
fn cc_env(var_base: &str, triple: &str) -> (String, Option<String>) {
|
||||
#[inline]
|
||||
fn env_var_with_key(key: String) -> Option<(String, String)> {
|
||||
std::env::var(&key).map(|value| (key, value)).ok()
|
||||
}
|
||||
|
||||
let triple_u = triple.replace('-', "_");
|
||||
let most_specific_key = format!("{}_{}", var_base, triple);
|
||||
|
||||
env_var_with_key(most_specific_key.to_string())
|
||||
.or_else(|| env_var_with_key(format!("{}_{}", var_base, triple_u)))
|
||||
.or_else(|| env_var_with_key(format!("TARGET_{}", var_base)))
|
||||
.or_else(|| env_var_with_key(var_base.to_string()))
|
||||
.map(|(key, value)| (key, Some(value)))
|
||||
.unwrap_or_else(|| (most_specific_key, None))
|
||||
}
|
||||
|
||||
fn cargo_env_target_cfg(triple: &str, key: &str) -> String {
|
||||
format!("CARGO_TARGET_{}_{}", &triple.replace('-', "_"), key).to_uppercase()
|
||||
}
|
||||
|
||||
fn clang_target(rust_target: &str, api_level: u8) -> String {
|
||||
let target = match rust_target {
|
||||
"arm-linux-androideabi" => "armv7a-linux-androideabi",
|
||||
"armv7-linux-androideabi" => "armv7a-linux-androideabi",
|
||||
_ => rust_target,
|
||||
};
|
||||
format!("--target={target}{api_level}")
|
||||
}
|
||||
|
||||
fn sysroot_target(rust_target: &str) -> &str {
|
||||
(match rust_target {
|
||||
"armv7-linux-androideabi" => "arm-linux-androideabi",
|
||||
_ => rust_target,
|
||||
}) as _
|
||||
}
|
||||
fn rt_builtins(rust_target: &str) -> &str {
|
||||
(match rust_target {
|
||||
"armv7-linux-androideabi" => "arm",
|
||||
"aarch64-linux-android" => "aarch64",
|
||||
"i686-linux-android" => "i686",
|
||||
"x86_64-linux-android" => "x86_64",
|
||||
_ => rust_target,
|
||||
}) as _
|
||||
}
|
||||
|
||||
let mut env_vars: Vec<(Cow<'static, str>, String)> = vec![];
|
||||
|
||||
let tools = self.workspace.android_tools()?;
|
||||
@@ -2361,7 +2481,7 @@ impl BuildRequest {
|
||||
let target_cc = tools.target_cc();
|
||||
let target_cxx = tools.target_cxx();
|
||||
let java_home = tools.java_home();
|
||||
let ndk = tools.ndk.clone();
|
||||
let ndk_home = tools.ndk.clone();
|
||||
tracing::debug!(
|
||||
r#"Using android:
|
||||
min_sdk_version: {min_sdk_version}
|
||||
@@ -2372,61 +2492,153 @@ impl BuildRequest {
|
||||
java_home: {java_home:?}
|
||||
"#
|
||||
);
|
||||
env_vars.push((
|
||||
"ANDROID_NATIVE_API_LEVEL".into(),
|
||||
min_sdk_version.to_string(),
|
||||
));
|
||||
env_vars.push(("TARGET_AR".into(), ar_path.display().to_string()));
|
||||
env_vars.push(("TARGET_CC".into(), target_cc.display().to_string()));
|
||||
env_vars.push(("TARGET_CXX".into(), target_cxx.display().to_string()));
|
||||
env_vars.push((
|
||||
format!(
|
||||
"CARGO_TARGET_{}_LINKER",
|
||||
self.triple
|
||||
.to_string()
|
||||
.to_ascii_uppercase()
|
||||
.replace("-", "_")
|
||||
)
|
||||
.into(),
|
||||
linker.display().to_string(),
|
||||
));
|
||||
env_vars.push(("ANDROID_NDK_ROOT".into(), ndk.display().to_string()));
|
||||
|
||||
if let Some(java_home) = java_home {
|
||||
tracing::debug!("Setting JAVA_HOME to {java_home:?}");
|
||||
env_vars.push(("JAVA_HOME".into(), java_home.display().to_string()));
|
||||
}
|
||||
|
||||
// Set the wry env vars - this is where wry will dump its kotlin files.
|
||||
// Their setup is really annyoing and requires us to hardcode `dx` to specific versions of tao/wry.
|
||||
env_vars.push(("WRY_ANDROID_PACKAGE".into(), "dev.dioxus.main".to_string()));
|
||||
env_vars.push(("WRY_ANDROID_LIBRARY".into(), "dioxusmain".to_string()));
|
||||
env_vars.push((
|
||||
"WRY_ANDROID_KOTLIN_FILES_OUT_DIR".into(),
|
||||
self.wry_android_kotlin_files_out_dir()
|
||||
.display()
|
||||
.to_string(),
|
||||
));
|
||||
let triple = self.triple.to_string();
|
||||
|
||||
// todo(jon): the guide for openssl recommends extending the path to include the tools dir
|
||||
// in practice I couldn't get this to work, but this might eventually become useful.
|
||||
//
|
||||
// https://github.com/openssl/openssl/blob/master/NOTES-ANDROID.md#configuration
|
||||
//
|
||||
// They recommend a configuration like this:
|
||||
//
|
||||
// // export ANDROID_NDK_ROOT=/home/whoever/Android/android-sdk/ndk/20.0.5594570
|
||||
// PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$PATH
|
||||
// ./Configure android-arm64 -D__ANDROID_API__=29
|
||||
// make
|
||||
//
|
||||
// let tools_dir = arch.android_tools_dir(&ndk);
|
||||
// let extended_path = format!(
|
||||
// "{}:{}",
|
||||
// tools_dir.display(),
|
||||
// std::env::var("PATH").unwrap_or_default()
|
||||
// );
|
||||
// env_vars.push(("PATH", extended_path));
|
||||
// Environment variables for the `cc` crate
|
||||
let (cc_key, _cc_value) = cc_env("CC", &triple);
|
||||
let (cflags_key, cflags_value) = cc_env("CFLAGS", &triple);
|
||||
let (cxx_key, _cxx_value) = cc_env("CXX", &triple);
|
||||
let (cxxflags_key, cxxflags_value) = cc_env("CXXFLAGS", &triple);
|
||||
let (ar_key, _ar_value) = cc_env("AR", &triple);
|
||||
let (ranlib_key, _ranlib_value) = cc_env("RANLIB", &triple);
|
||||
|
||||
// Environment variables for cargo
|
||||
let cargo_ar_key = cargo_env_target_cfg(&triple, "ar");
|
||||
let cargo_rust_flags_key = cargo_env_target_cfg(&triple, "rustflags");
|
||||
let bindgen_clang_args_key =
|
||||
format!("BINDGEN_EXTRA_CLANG_ARGS_{}", &triple.replace('-', "_"));
|
||||
|
||||
let clang_target = clang_target(&self.triple.to_string(), min_sdk_version as _);
|
||||
let target_cc = tools.target_cc();
|
||||
let target_cflags = match cflags_value {
|
||||
Some(v) => format!("{clang_target} {v}"),
|
||||
None => clang_target.to_string(),
|
||||
};
|
||||
let target_cxx = tools.target_cxx();
|
||||
let target_cxxflags = match cxxflags_value {
|
||||
Some(v) => format!("{clang_target} {v}"),
|
||||
None => clang_target.to_string(),
|
||||
};
|
||||
let cargo_ndk_sysroot_path_key = "CARGO_NDK_SYSROOT_PATH";
|
||||
let cargo_ndk_sysroot_path = tools.sysroot();
|
||||
let cargo_ndk_sysroot_target_key = "CARGO_NDK_SYSROOT_TARGET";
|
||||
let cargo_ndk_sysroot_target = sysroot_target(&triple);
|
||||
let cargo_ndk_sysroot_libs_path_key = "CARGO_NDK_SYSROOT_LIBS_PATH";
|
||||
let cargo_ndk_sysroot_libs_path = cargo_ndk_sysroot_path
|
||||
.join("usr")
|
||||
.join("lib")
|
||||
.join(cargo_ndk_sysroot_target);
|
||||
let target_ar = tools.ar_path();
|
||||
let target_ranlib = tools.ranlib();
|
||||
let clang_folder = tools.clang_folder();
|
||||
|
||||
// choose the clang target with the highest version
|
||||
// Should we filter for only numbers?
|
||||
let clang_builtins_target = std::fs::read_dir(clang_folder)
|
||||
.expect("Unable to get clang target directory")
|
||||
.filter_map(|a| a.ok())
|
||||
.max_by(|a, b| a.file_name().cmp(&b.file_name()))
|
||||
.expect("Unable to get clang target")
|
||||
.path();
|
||||
let clang_rt = format!(
|
||||
"-L{} -lstatic=clang_rt.builtins-{}-android",
|
||||
clang_builtins_target.join("lib").join("linux").display(),
|
||||
rt_builtins(&triple)
|
||||
);
|
||||
|
||||
let extra_include: String = format!(
|
||||
"{}/usr/include/{}",
|
||||
&cargo_ndk_sysroot_path.display(),
|
||||
&cargo_ndk_sysroot_target
|
||||
);
|
||||
|
||||
let bindgen_args = format!(
|
||||
"--sysroot={} -I{}",
|
||||
&cargo_ndk_sysroot_path.display(),
|
||||
extra_include
|
||||
);
|
||||
|
||||
for env in [
|
||||
(cc_key, target_cc.clone().into_os_string()),
|
||||
(cflags_key, target_cflags.into()),
|
||||
(cxx_key, target_cxx.into_os_string()),
|
||||
(cxxflags_key, target_cxxflags.into()),
|
||||
(ar_key, target_ar.clone().into()),
|
||||
(ranlib_key, target_ranlib.into_os_string()),
|
||||
(cargo_ar_key, target_ar.into_os_string()),
|
||||
(
|
||||
cargo_ndk_sysroot_path_key.to_string(),
|
||||
cargo_ndk_sysroot_path.clone().into_os_string(),
|
||||
),
|
||||
(
|
||||
cargo_ndk_sysroot_libs_path_key.to_string(),
|
||||
cargo_ndk_sysroot_libs_path.into_os_string(),
|
||||
),
|
||||
(
|
||||
cargo_ndk_sysroot_target_key.to_string(),
|
||||
cargo_ndk_sysroot_target.into(),
|
||||
),
|
||||
(cargo_rust_flags_key, clang_rt.into()),
|
||||
(bindgen_clang_args_key, bindgen_args.into()),
|
||||
(
|
||||
"ANDROID_NATIVE_API_LEVEL".to_string(),
|
||||
min_sdk_version.to_string().into(),
|
||||
),
|
||||
(
|
||||
format!(
|
||||
"CARGO_TARGET_{}_LINKER",
|
||||
self.triple
|
||||
.to_string()
|
||||
.to_ascii_uppercase()
|
||||
.replace("-", "_")
|
||||
),
|
||||
linker.into_os_string(),
|
||||
),
|
||||
("ANDROID_NDK_ROOT".to_string(), ndk_home.into_os_string()),
|
||||
// Set the wry env vars - this is where wry will dump its kotlin files.
|
||||
// Their setup is really annyoing and requires us to hardcode `dx` to specific versions of tao/wry.
|
||||
(
|
||||
"WRY_ANDROID_PACKAGE".to_string(),
|
||||
"dev.dioxus.main".to_string().into(),
|
||||
),
|
||||
(
|
||||
"WRY_ANDROID_LIBRARY".to_string(),
|
||||
"dioxusmain".to_string().into(),
|
||||
),
|
||||
(
|
||||
"WRY_ANDROID_KOTLIN_FILES_OUT_DIR".to_string(),
|
||||
self.wry_android_kotlin_files_out_dir().into_os_string(),
|
||||
),
|
||||
// Found this through a comment related to bindgen using the wrong clang for cross compiles
|
||||
//
|
||||
// https://github.com/rust-lang/rust-bindgen/issues/2962#issuecomment-2438297124
|
||||
//
|
||||
// https://github.com/KyleMayes/clang-sys?tab=readme-ov-file#environment-variables
|
||||
("CLANG_PATH".into(), target_cc.with_extension("exe").into()),
|
||||
] {
|
||||
env_vars.push((
|
||||
env.0.into(),
|
||||
env.1
|
||||
.to_str()
|
||||
.expect("Failed to convert env var value to string")
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if std::env::var("MSYSTEM").is_ok() || std::env::var("CYGWIN").is_ok() {
|
||||
for var in env_vars.iter_mut() {
|
||||
// Convert windows paths to unix-style paths
|
||||
// This is a workaround for the fact that the `cc` crate expects unix-style paths
|
||||
// and will fail if it encounters windows-style paths.
|
||||
var.1 = var.1.replace('\\', "/");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(env_vars)
|
||||
}
|
||||
@@ -2465,7 +2677,7 @@ impl BuildRequest {
|
||||
|
||||
let output = tokio::process::Command::new("cargo")
|
||||
.arg("+nightly")
|
||||
.arg("build")
|
||||
.arg("rustc")
|
||||
.arg("--unit-graph")
|
||||
.arg("-Z")
|
||||
.arg("unstable-options")
|
||||
@@ -2479,7 +2691,11 @@ impl BuildRequest {
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to get unit count").into());
|
||||
tracing::trace!(
|
||||
"Failed to get unit count: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
bail!("Failed to get unit count");
|
||||
}
|
||||
|
||||
let output_text = String::from_utf8(output.stdout).context("Failed to get unit count")?;
|
||||
@@ -2911,15 +3127,6 @@ impl BuildRequest {
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the version of the wasm-bindgen crate if it exists
|
||||
fn wasm_bindgen_version(&self) -> Option<String> {
|
||||
self.workspace
|
||||
.krates
|
||||
.krates_by_name("wasm-bindgen")
|
||||
.next()
|
||||
.map(|krate| krate.krate.version.to_string())
|
||||
}
|
||||
|
||||
/// Return the platforms that are enabled for the package
|
||||
///
|
||||
/// Ideally only one platform is enabled but we need to be able to
|
||||
@@ -3224,6 +3431,7 @@ impl BuildRequest {
|
||||
let post_bindgen_wasm = self.wasm_bindgen_wasm_output_file();
|
||||
let should_bundle_split: bool = self.wasm_split;
|
||||
let bindgen_version = self
|
||||
.workspace
|
||||
.wasm_bindgen_version()
|
||||
.expect("this should have been checked by tool verification");
|
||||
|
||||
@@ -3291,10 +3499,7 @@ impl BuildRequest {
|
||||
ctx.status_splitting_bundle();
|
||||
|
||||
if !will_wasm_opt {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Bundle splitting should automatically enable wasm-opt, but it was not enabled."
|
||||
)
|
||||
.into());
|
||||
bail!("Bundle splitting should automatically enable wasm-opt, but it was not enabled.");
|
||||
}
|
||||
|
||||
// Load the contents of these binaries since we need both of them
|
||||
@@ -3549,7 +3754,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.into()),
|
||||
_ => Err(anyhow::anyhow!("Unsupported platform for Info.plist").into()),
|
||||
_ => Err(anyhow::anyhow!("Unsupported platform for Info.plist")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3574,7 +3779,10 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to assemble apk: {output:?}").into());
|
||||
bail!(
|
||||
"Failed to assemble apk: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3593,7 +3801,10 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
.context("Failed to run gradle bundleRelease")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to bundleRelease: {output:?}").into());
|
||||
bail!(
|
||||
"Failed to bundleRelease: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
let app_release = self
|
||||
@@ -3688,7 +3899,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
});
|
||||
|
||||
if let Err(e) = success.as_ref() {
|
||||
return Err(format!("Failed to initialize build directory: {e}").into());
|
||||
bail!("Failed to initialize build directory: {e}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -3816,15 +4027,16 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
|
||||
// Ensure target is installed.
|
||||
if !self.workspace.has_wasm32_unknown_unknown() {
|
||||
return Err(Error::Other(anyhow::anyhow!(
|
||||
"Missing target wasm32-unknown-unknown."
|
||||
)));
|
||||
bail!("Missing target wasm32-unknown-unknown.");
|
||||
}
|
||||
|
||||
// Wasm bindgen
|
||||
let krate_bindgen_version = self.wasm_bindgen_version().ok_or(anyhow::anyhow!(
|
||||
"failed to detect wasm-bindgen version, unable to proceed"
|
||||
))?;
|
||||
let krate_bindgen_version =
|
||||
self.workspace
|
||||
.wasm_bindgen_version()
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"failed to detect wasm-bindgen version, unable to proceed"
|
||||
))?;
|
||||
|
||||
WasmBindgen::verify_install(&krate_bindgen_version).await?;
|
||||
|
||||
@@ -3884,9 +4096,9 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(anyhow::anyhow!(
|
||||
"Android linker not found. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation."
|
||||
).into())
|
||||
bail!(
|
||||
"Android linker not found at {linker:?}. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation."
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure the right dependencies are installed for linux apps.
|
||||
@@ -4347,4 +4559,20 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
.flat_map(|arg| ["--config".to_string(), arg])
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn prebuild(&self) -> Result<()> {
|
||||
if self.platform == Platform::Server {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Run the tailwind build before bundling anything else
|
||||
crate::TailwindCli::run_once(
|
||||
self.package_manifest_dir(),
|
||||
self.config.application.tailwind_input.clone(),
|
||||
self.config.application.tailwind_output.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,13 @@ impl AndroidTools {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn sysroot(&self) -> PathBuf {
|
||||
// The sysroot is usually located in the NDK under:
|
||||
// "~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/sysroot"
|
||||
// or similar, depending on the platform.
|
||||
self.android_tools_dir().parent().unwrap().join("sysroot")
|
||||
}
|
||||
|
||||
pub(crate) fn sdk(&self) -> PathBuf {
|
||||
// /Users/jonathankelley/Library/Android/sdk/ndk/25.2/... (25.2 is the ndk here)
|
||||
// /Users/jonathankelley/Library/Android/sdk/
|
||||
@@ -187,6 +194,21 @@ impl AndroidTools {
|
||||
24
|
||||
}
|
||||
|
||||
pub(crate) fn clang_folder(&self) -> PathBuf {
|
||||
// The clang folder is usually located in the NDK under:
|
||||
// "~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/<version>"
|
||||
// or similar, depending on the platform.
|
||||
self.android_tools_dir()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("lib")
|
||||
.join("clang")
|
||||
}
|
||||
|
||||
pub(crate) fn ranlib(&self) -> PathBuf {
|
||||
self.android_tools_dir().join("llvm-ranlib")
|
||||
}
|
||||
|
||||
pub(crate) fn ar_path(&self) -> PathBuf {
|
||||
self.android_tools_dir().join("llvm-ar")
|
||||
}
|
||||
@@ -261,6 +283,24 @@ impl AndroidTools {
|
||||
|
||||
triple
|
||||
}
|
||||
|
||||
pub(crate) fn libcpp_shared(&self, triple: &Triple) -> PathBuf {
|
||||
// The libc++_shared.so is usually located in the sysroot under:
|
||||
// "~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/<arch>/libc++_shared.so"
|
||||
// or similar, depending on the platform.
|
||||
self.sysroot()
|
||||
.join("usr")
|
||||
.join("lib")
|
||||
.join(Self::sysroot_target(&triple.to_string()))
|
||||
.join("libc++_shared.so")
|
||||
}
|
||||
|
||||
pub(crate) fn sysroot_target(rust_target: &str) -> &str {
|
||||
(match rust_target {
|
||||
"armv7-linux-androideabi" => "arm-linux-androideabi",
|
||||
_ => rust_target,
|
||||
}) as _
|
||||
}
|
||||
}
|
||||
|
||||
fn var_or_debug(name: &str) -> Option<PathBuf> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{check::collect_rs_files, *};
|
||||
use crate::Workspace;
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||
use rayon::prelude::*;
|
||||
use std::{borrow::Cow, fs, path::Path};
|
||||
@@ -54,11 +54,9 @@ impl Autoformat {
|
||||
} else if let Some(raw) = raw {
|
||||
// Format raw text.
|
||||
let indent = indentation_for(".", self.split_line_attributes)?;
|
||||
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
|
||||
println!("{}", inner);
|
||||
} else {
|
||||
return Err("error formatting codeblock".into());
|
||||
}
|
||||
let formatted =
|
||||
dioxus_autofmt::fmt_block(&raw, 0, indent).context("error formatting codeblock")?;
|
||||
println!("{}", formatted);
|
||||
} else {
|
||||
// Default to formatting the project.
|
||||
let crate_dir = if let Some(package) = self.package {
|
||||
@@ -76,11 +74,8 @@ impl Autoformat {
|
||||
Cow::Borrowed(Path::new("."))
|
||||
};
|
||||
|
||||
if let Err(e) =
|
||||
autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
|
||||
{
|
||||
return Err(format!("error formatting project: {}", e).into());
|
||||
}
|
||||
autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
|
||||
.context("error autoformatting project")?;
|
||||
}
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
@@ -100,19 +95,15 @@ fn refactor_file(
|
||||
} else {
|
||||
fs::read_to_string(&file)
|
||||
};
|
||||
let Ok(mut s) = file_content else {
|
||||
return Err(format!("failed to open file: {}", file_content.unwrap_err()).into());
|
||||
};
|
||||
let mut s = file_content.context("failed to open file")?;
|
||||
|
||||
if format_rust_code {
|
||||
s = format_rust(&s)?;
|
||||
}
|
||||
|
||||
let Ok(Ok(edits)) =
|
||||
syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent))
|
||||
else {
|
||||
return Err(format!("failed to format file: {}", s).into());
|
||||
};
|
||||
let parsed = syn::parse_file(&s).context("failed to parse file")?;
|
||||
let edits =
|
||||
dioxus_autofmt::try_fmt_file(&s, &parsed, indent).context("failed to format file")?;
|
||||
|
||||
let out = dioxus_autofmt::apply_formats(&s, edits);
|
||||
|
||||
@@ -135,18 +126,16 @@ fn format_file(
|
||||
let mut contents = fs::read_to_string(&path)?;
|
||||
let mut if_write = false;
|
||||
if format_rust_code {
|
||||
let formatted = format_rust(&contents)
|
||||
.map_err(|err| Error::Parse(format!("Syntax Error:\n{}", err)))?;
|
||||
let formatted = format_rust(&contents).context("Syntax Error")?;
|
||||
if contents != formatted {
|
||||
if_write = true;
|
||||
contents = formatted;
|
||||
}
|
||||
}
|
||||
|
||||
let parsed = syn::parse_file(&contents)
|
||||
.map_err(|err| Error::Parse(format!("Failed to parse file: {}", err)))?;
|
||||
let parsed = syn::parse_file(&contents).context("Failed to parse file")?;
|
||||
let edits = dioxus_autofmt::try_fmt_file(&contents, &parsed, indent)
|
||||
.map_err(|err| Error::Parse(format!("Failed to format file: {}", err)))?;
|
||||
.context("Failed to format file")?;
|
||||
let len = edits.len();
|
||||
|
||||
if !edits.is_empty() {
|
||||
@@ -202,7 +191,7 @@ fn autoformat_project(
|
||||
let files_formatted: usize = counts.into_iter().flatten().sum();
|
||||
|
||||
if files_formatted > 0 && check {
|
||||
return Err(format!("{} files needed formatting", files_formatted).into());
|
||||
bail!("{} files needed formatting", files_formatted);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -220,9 +209,7 @@ fn indentation_for(
|
||||
.output()?;
|
||||
|
||||
if !out.status.success() {
|
||||
return Err(Error::Runtime(format!(
|
||||
"cargo fmt failed with status: {out:?}"
|
||||
)));
|
||||
bail!("cargo fmt failed with status: {out:?}");
|
||||
}
|
||||
|
||||
let config = String::from_utf8_lossy(&out.stdout);
|
||||
@@ -232,18 +219,14 @@ fn indentation_for(
|
||||
.find(|line| line.starts_with("hard_tabs "))
|
||||
.and_then(|line| line.split_once('='))
|
||||
.map(|(_, value)| value.trim() == "true")
|
||||
.ok_or_else(|| {
|
||||
Error::Runtime("Could not find hard_tabs option in rustfmt config".into())
|
||||
})?;
|
||||
.context("Could not find hard_tabs option in rustfmt config")?;
|
||||
let tab_spaces = config
|
||||
.lines()
|
||||
.find(|line| line.starts_with("tab_spaces "))
|
||||
.and_then(|line| line.split_once('='))
|
||||
.map(|(_, value)| value.trim().parse::<usize>())
|
||||
.ok_or_else(|| Error::Runtime("Could not find tab_spaces option in rustfmt config".into()))?
|
||||
.map_err(|_| {
|
||||
Error::Runtime("Could not parse tab_spaces option in rustfmt config".into())
|
||||
})?;
|
||||
.context("Could not find tab_spaces option in rustfmt config")?
|
||||
.context("Could not parse tab_spaces option in rustfmt config")?;
|
||||
|
||||
Ok(IndentOptions::new(
|
||||
if hard_tabs {
|
||||
@@ -258,7 +241,9 @@ fn indentation_for(
|
||||
|
||||
/// Format rust code using prettyplease
|
||||
fn format_rust(input: &str) -> Result<String> {
|
||||
let syntax_tree = syn::parse_file(input).map_err(format_syn_error)?;
|
||||
let syntax_tree = syn::parse_file(input)
|
||||
.map_err(format_syn_error)
|
||||
.context("Failed to parse Rust syntax")?;
|
||||
let output = prettyplease::unparse(&syntax_tree);
|
||||
Ok(output)
|
||||
}
|
||||
@@ -267,10 +252,7 @@ fn format_syn_error(err: syn::Error) -> Error {
|
||||
let start = err.span().start();
|
||||
let line = start.line;
|
||||
let column = start.column;
|
||||
Error::Parse(format!(
|
||||
"Syntax Error in line {} column {}:\n{}",
|
||||
line, column, err
|
||||
))
|
||||
anyhow::anyhow!("Syntax Error in line {} column {}:\n{}", line, column, err)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -70,7 +70,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
|
||||
let ssg = self.shared.ssg;
|
||||
let targets = self.into_targets().await?;
|
||||
|
||||
AppBuilder::start(&targets.client, BuildMode::Base)?
|
||||
AppBuilder::started(&targets.client, BuildMode::Base)?
|
||||
.finish_build()
|
||||
.await?;
|
||||
|
||||
@@ -78,7 +78,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
|
||||
|
||||
if let Some(server) = targets.server.as_ref() {
|
||||
// If the server is present, we need to build it as well
|
||||
let mut server_build = AppBuilder::start(server, BuildMode::Base)?;
|
||||
let mut server_build = AppBuilder::started(server, BuildMode::Base)?;
|
||||
server_build.finish_build().await?;
|
||||
|
||||
// Run SSG and cache static routes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform};
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{bail, Context};
|
||||
use path_absolutize::Absolutize;
|
||||
use std::collections::HashMap;
|
||||
use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
|
||||
@@ -39,7 +39,7 @@ impl Bundle {
|
||||
|
||||
let BuildTargets { client, server } = self.args.into_targets().await?;
|
||||
|
||||
AppBuilder::start(&client, BuildMode::Base)?
|
||||
AppBuilder::started(&client, BuildMode::Base)?
|
||||
.finish_build()
|
||||
.await?;
|
||||
|
||||
@@ -47,7 +47,7 @@ impl Bundle {
|
||||
|
||||
if let Some(server) = server.as_ref() {
|
||||
// If the server is present, we need to build it as well
|
||||
AppBuilder::start(server, BuildMode::Base)?
|
||||
AppBuilder::started(server, BuildMode::Base)?
|
||||
.finish_build()
|
||||
.await?;
|
||||
|
||||
@@ -167,10 +167,10 @@ impl Bundle {
|
||||
|
||||
// Check if required fields are provided instead of failing silently.
|
||||
if bundle_settings.identifier.is_none() {
|
||||
return Err(anyhow!("\n\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\nidentifier = \"com.mycompany\"\n\n").into());
|
||||
bail!("\n\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\nidentifier = \"com.mycompany\"\n\n");
|
||||
}
|
||||
if bundle_settings.publisher.is_none() {
|
||||
return Err(anyhow!("\n\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\npublisher = \"MyCompany\"\n\n").into());
|
||||
bail!("\n\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\npublisher = \"MyCompany\"\n\n");
|
||||
}
|
||||
|
||||
if cfg!(windows) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use super::*;
|
||||
use crate::BuildRequest;
|
||||
use anyhow::Context;
|
||||
use anyhow::{anyhow, Context};
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -118,8 +118,8 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
|
||||
tracing::info!("No issues found.");
|
||||
Ok(())
|
||||
}
|
||||
1 => Err("1 issue found.".into()),
|
||||
_ => Err(format!("{} issues found.", total_issues).into()),
|
||||
1 => Err(anyhow!("1 issue found.")),
|
||||
_ => Err(anyhow!("{} issues found.", total_issues)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
use super::*;
|
||||
use crate::Result;
|
||||
|
||||
/// Clean build artifacts.
|
||||
///
|
||||
/// Simply runs `cargo clean`
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
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) async fn clean(self) -> Result<StructuredOutput> {
|
||||
let output = tokio::process::Command::new("cargo")
|
||||
.arg("clean")
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Cargo clean failed.").into());
|
||||
}
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::TraceSrc;
|
||||
use anyhow::{bail, Context};
|
||||
use cargo_generate::{GenerateArgs, TemplatePath, Vcs};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -147,9 +148,9 @@ pub(crate) fn name_from_path(path: &Path) -> Result<String> {
|
||||
.absolutize()?
|
||||
.to_path_buf()
|
||||
.file_name()
|
||||
.ok_or("Current path does not include directory name".to_string())?
|
||||
.context("Current path does not include directory name".to_string())?
|
||||
.to_str()
|
||||
.ok_or("Current directory name is not a valid UTF-8 string".to_string())?
|
||||
.context("Current directory name is not a valid UTF-8 string".to_string())?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
@@ -167,10 +168,7 @@ pub(crate) fn post_create(path: &Path, vcs: &Vcs) -> Result<()> {
|
||||
// Only 1 error means that CWD isn't a cargo project.
|
||||
Err(cargo_metadata::Error::CargoMetadata { .. }) => None,
|
||||
Err(err) => {
|
||||
return Err(Error::Other(anyhow::anyhow!(
|
||||
"Couldn't retrieve cargo metadata: {:?}",
|
||||
err
|
||||
)));
|
||||
anyhow::bail!("Couldn't retrieve cargo metadata: {:?}", err)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -286,9 +284,9 @@ pub(crate) async fn connectivity_check() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Network(
|
||||
"Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project.".to_string(),
|
||||
))
|
||||
bail!(
|
||||
"Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project."
|
||||
)
|
||||
}
|
||||
|
||||
// todo: re-enable these tests with better parallelization
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
use super::*;
|
||||
use crate::{Result, Workspace};
|
||||
use anyhow::{bail, Context};
|
||||
use itertools::Itertools;
|
||||
|
||||
/// Perform a system analysis to verify the system install is working correctly.
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub(crate) struct Doctor {}
|
||||
|
||||
impl Doctor {
|
||||
pub async fn doctor(self) -> Result<StructuredOutput> {
|
||||
let Ok(workspace) = Workspace::current().await else {
|
||||
bail!("dx doctor must be run within a cargo workspace!")
|
||||
};
|
||||
|
||||
let rustc_version = &workspace.rustc_version;
|
||||
let rustc_sysroot = &workspace.sysroot.to_string_lossy();
|
||||
let rustlib = workspace.sysroot.join("lib").join("rustlib");
|
||||
|
||||
// wasm-opt
|
||||
let wasm_opt_location = crate::wasm_opt::installed_location();
|
||||
let wasm_opt_message = match wasm_opt_location.clone() {
|
||||
Some(path) => path.to_string_lossy().to_string(),
|
||||
None => "not installed".into(),
|
||||
};
|
||||
|
||||
// wasm-bindgen
|
||||
let wbg_version = workspace.wasm_bindgen_version();
|
||||
let wbg_version_msg = match wbg_version.clone() {
|
||||
Some(msg) => msg,
|
||||
None => "not required".to_string(),
|
||||
};
|
||||
let wasm_bindgen_location = match wbg_version {
|
||||
Some(vers) => match crate::wasm_bindgen::WasmBindgen::new(&vers).get_binary_path() {
|
||||
Ok(path) => path.to_string_lossy().to_string(),
|
||||
Err(err) => err.to_string().lines().join(""),
|
||||
},
|
||||
None => "not required".to_string(),
|
||||
};
|
||||
|
||||
// extensions
|
||||
fn has_dioxus_ext(editor_dir: &str) -> anyhow::Result<PathBuf> {
|
||||
let home = dirs::home_dir().context("no home dir")?;
|
||||
let exts = home.join(editor_dir).join("extensions");
|
||||
for dir in exts.read_dir()?.flatten() {
|
||||
if dir
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.contains("dioxuslabs.dioxus-")
|
||||
{
|
||||
return Ok(dir.path());
|
||||
}
|
||||
}
|
||||
|
||||
bail!("not found")
|
||||
}
|
||||
|
||||
// Editors
|
||||
let vscode_ext = has_dioxus_ext(".vscode");
|
||||
let vscode_ext_msg = match vscode_ext.as_ref() {
|
||||
Ok(path) => path.to_string_lossy().to_string(),
|
||||
Err(_) => "not found".to_string(),
|
||||
};
|
||||
let cursor_ext = has_dioxus_ext(".cursor");
|
||||
let cursor_ext_msg = match cursor_ext.as_ref() {
|
||||
Ok(path) => path.to_string_lossy().to_string(),
|
||||
Err(_) => "not found".to_string(),
|
||||
};
|
||||
|
||||
// Tailwind
|
||||
let mut tailwindcss = "not found".to_string();
|
||||
if let Ok(path) = crate::tailwind::TailwindCli::v3().get_binary_path() {
|
||||
tailwindcss = path.display().to_string();
|
||||
}
|
||||
if let Ok(path) = crate::tailwind::TailwindCli::v4().get_binary_path() {
|
||||
tailwindcss = path.display().to_string();
|
||||
}
|
||||
|
||||
let mut adb = "not found".to_string();
|
||||
let mut ndk = "not found".to_string();
|
||||
let mut sdk = "not found".to_string();
|
||||
let mut java_home = "not found".to_string();
|
||||
let mut emulator = "not found".to_string();
|
||||
if let Some(rf) = workspace.android_tools.as_deref() {
|
||||
if rf.adb.exists() {
|
||||
adb = rf.adb.display().to_string();
|
||||
}
|
||||
if rf.ndk.exists() {
|
||||
ndk = rf.ndk.display().to_string();
|
||||
}
|
||||
if let Some(jh) = rf.java_home.as_ref() {
|
||||
java_home = jh.display().to_string();
|
||||
}
|
||||
if rf.sdk().exists() {
|
||||
sdk = rf.sdk().display().to_string();
|
||||
}
|
||||
if let Some(jh) = rf.java_home.as_ref() {
|
||||
java_home = jh.display().to_string();
|
||||
}
|
||||
if rf.emulator().exists() {
|
||||
emulator = rf.emulator().display().to_string();
|
||||
}
|
||||
};
|
||||
|
||||
let mut simulator_location = "not found".to_string();
|
||||
let mut xcode_install = "not found".to_string();
|
||||
if let Some(xcode) = workspace.xcode.as_ref() {
|
||||
let sim_location = xcode.join("Applications").join("Simulator.app");
|
||||
if sim_location.exists() {
|
||||
simulator_location = sim_location.display().to_string();
|
||||
}
|
||||
if xcode.exists() {
|
||||
xcode_install = xcode.display().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// toolchains
|
||||
let mut has_wasm32_unknown_unknown = "❌";
|
||||
let mut has_aarch64_android_linux = "❌";
|
||||
let mut has_i686_linux_android = "❌";
|
||||
let mut has_armv7_linux_androideabi = "❌";
|
||||
let mut has_x86_64_android_linux = "❌";
|
||||
let mut has_x86_64_apple_ios = "❌";
|
||||
let mut has_aarch64_apple_ios = "❌";
|
||||
let mut has_aarch64_apple_ios_sim = "❌";
|
||||
let mut has_aarch64_apple_darwin = "❌";
|
||||
if rustlib.join("wasm32-unknown-unknown").exists() {
|
||||
has_wasm32_unknown_unknown = "✅";
|
||||
}
|
||||
if rustlib.join("aarch64-linux-android").exists() {
|
||||
has_aarch64_android_linux = "✅";
|
||||
}
|
||||
if rustlib.join("i686-linux-android").exists() {
|
||||
has_i686_linux_android = "✅";
|
||||
}
|
||||
if rustlib.join("armv7-linux-androideabi").exists() {
|
||||
has_armv7_linux_androideabi = "✅";
|
||||
}
|
||||
if rustlib.join("x86_64-linux-android").exists() {
|
||||
has_x86_64_android_linux = "✅";
|
||||
}
|
||||
if rustlib.join("x86_64-apple-ios").exists() {
|
||||
has_x86_64_apple_ios = "✅";
|
||||
}
|
||||
if rustlib.join("aarch64-apple-ios").exists() {
|
||||
has_aarch64_apple_ios = "✅";
|
||||
}
|
||||
if rustlib.join("aarch64-apple-ios-sim").exists() {
|
||||
has_aarch64_apple_ios_sim = "✅";
|
||||
}
|
||||
if rustlib.join("aarch64-apple-darwin").exists() {
|
||||
has_aarch64_apple_darwin = "✅";
|
||||
}
|
||||
|
||||
// Things to know
|
||||
// - current rust version and rust-related things
|
||||
// - installed toolchains
|
||||
// -
|
||||
use crate::styles::*;
|
||||
println!(
|
||||
r#"{LINK_STYLE}Setup{LINK_STYLE:#}
|
||||
{GLOW_STYLE}Web{GLOW_STYLE:#}: wasm-bindgen, wasm-opt, and TailwindCSS are downloaded automatically
|
||||
{GLOW_STYLE}iOS{GLOW_STYLE:#}: Install iOS SDK and developer tools and through XCode
|
||||
{GLOW_STYLE}Android{GLOW_STYLE:#}: Install Android Studio, NDK, and then set ANDROID_HOME and ANDROID_NDK_HOME
|
||||
{GLOW_STYLE}macOS{GLOW_STYLE:#}: all tools should be installed by default
|
||||
{GLOW_STYLE}Windows{GLOW_STYLE:#}: install the webview2 binary
|
||||
{GLOW_STYLE}Linux{GLOW_STYLE:#}: Install libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev
|
||||
|
||||
{LINK_STYLE}Rust{LINK_STYLE:#}
|
||||
Rustc version: {HINT_STYLE}{rustc_version}{HINT_STYLE:#}
|
||||
Rustc sysroot: {HINT_STYLE}{rustc_sysroot}{HINT_STYLE:#}
|
||||
|
||||
{LINK_STYLE}Devtools{LINK_STYLE:#}
|
||||
VSCode Extension: {HINT_STYLE}{vscode_ext_msg}{HINT_STYLE:#}
|
||||
Cursor Extension: {HINT_STYLE}{cursor_ext_msg}{HINT_STYLE:#}
|
||||
TailwindCSS: {HINT_STYLE}{tailwindcss}{HINT_STYLE:#}
|
||||
|
||||
{LINK_STYLE}Web{LINK_STYLE:#}
|
||||
wasm-opt: {HINT_STYLE}{wasm_opt_message}{HINT_STYLE:#}
|
||||
wasm-bindgen: {HINT_STYLE}{wasm_bindgen_location}{HINT_STYLE:#}
|
||||
wasm-bindgen version: {HINT_STYLE}{wbg_version_msg}{HINT_STYLE:#}
|
||||
|
||||
{LINK_STYLE}iOS{LINK_STYLE:#}
|
||||
XCode: {HINT_STYLE}{xcode_install}{HINT_STYLE:#}
|
||||
Simulator: {HINT_STYLE}{simulator_location}{HINT_STYLE:#}
|
||||
|
||||
{LINK_STYLE}Android{LINK_STYLE:#}
|
||||
sdk: {HINT_STYLE}{sdk}{HINT_STYLE:#}
|
||||
ndk: {HINT_STYLE}{ndk}{HINT_STYLE:#}
|
||||
adb: {HINT_STYLE}{adb}{HINT_STYLE:#}
|
||||
emulator: {HINT_STYLE}{emulator}{HINT_STYLE:#}
|
||||
java_home: {HINT_STYLE}{java_home}{HINT_STYLE:#}
|
||||
|
||||
{LINK_STYLE}Toolchains{LINK_STYLE:#}
|
||||
{HINT_STYLE}{has_wasm32_unknown_unknown}{HINT_STYLE:#} wasm32-unknown-unknown {HINT_STYLE}(web){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_aarch64_android_linux}{HINT_STYLE:#} aarch64-android-linux {HINT_STYLE}(android){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_i686_linux_android}{HINT_STYLE:#} i686-linux-android {HINT_STYLE}(android){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_armv7_linux_androideabi}{HINT_STYLE:#} armv7-linux-androideabi {HINT_STYLE}(android){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_x86_64_android_linux}{HINT_STYLE:#} x86_64-android-linux {HINT_STYLE}(android){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_x86_64_apple_ios}{HINT_STYLE:#} x86_64-apple-ios {HINT_STYLE}(iOS){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_aarch64_apple_ios}{HINT_STYLE:#} aarch64-apple-ios {HINT_STYLE}(iOS){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_aarch64_apple_ios_sim}{HINT_STYLE:#} aarch64-apple-ios-sim {HINT_STYLE}(iOS){HINT_STYLE:#}
|
||||
{HINT_STYLE}{has_aarch64_apple_darwin}{HINT_STYLE:#} aarch64-apple-darwin {HINT_STYLE}(iOS){HINT_STYLE:#}
|
||||
|
||||
Get help: {LINK_STYLE}https://discord.gg/XgGxMSkvUM{LINK_STYLE:#}
|
||||
More info: {LINK_STYLE}https://dioxuslabs.com/learn/0.6/{LINK_STYLE:#}
|
||||
"#
|
||||
);
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ pub(crate) mod build;
|
||||
pub(crate) mod build_assets;
|
||||
pub(crate) mod bundle;
|
||||
pub(crate) mod check;
|
||||
pub(crate) mod clean;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod create;
|
||||
pub(crate) mod doctor;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod link;
|
||||
pub(crate) mod platform_override;
|
||||
@@ -75,9 +75,9 @@ pub(crate) enum Commands {
|
||||
#[clap(name = "init")]
|
||||
Init(init::Init),
|
||||
|
||||
/// Clean output artifacts.
|
||||
#[clap(name = "clean")]
|
||||
Clean(clean::Clean),
|
||||
/// Diagnose installed tools and system configuration.
|
||||
#[clap(name = "doctor")]
|
||||
Doctor(doctor::Doctor),
|
||||
|
||||
/// Translate a source file into Dioxus code.
|
||||
#[clap(name = "translate")]
|
||||
@@ -121,7 +121,6 @@ impl Display for Commands {
|
||||
Commands::Serve(_) => write!(f, "serve"),
|
||||
Commands::New(_) => write!(f, "create"),
|
||||
Commands::Init(_) => write!(f, "init"),
|
||||
Commands::Clean(_) => write!(f, "clean"),
|
||||
Commands::Config(_) => write!(f, "config"),
|
||||
Commands::Autoformat(_) => write!(f, "fmt"),
|
||||
Commands::Check(_) => write!(f, "check"),
|
||||
@@ -129,6 +128,7 @@ impl Display for Commands {
|
||||
Commands::Run(_) => write!(f, "run"),
|
||||
Commands::SelfUpdate(_) => write!(f, "self-update"),
|
||||
Commands::Tools(_) => write!(f, "tools"),
|
||||
Commands::Doctor(_) => write!(f, "doctor"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,4 +169,5 @@ pub mod styles {
|
||||
pub(crate) const NOTE_STYLE: Style = AnsiColor::Green.on_default();
|
||||
pub(crate) const LINK_STYLE: Style = AnsiColor::Blue.on_default();
|
||||
pub(crate) const ERROR_STYLE: Style = AnsiColor::Red.on_default();
|
||||
pub(crate) const HINT_STYLE: Style = clap::builder::styling::Ansi256Color(244).on_default();
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
serve::{AppServer, ServeUpdate, WebServer},
|
||||
BuilderUpdate, Error, Platform, Result,
|
||||
BuilderUpdate, Platform, Result,
|
||||
};
|
||||
use anyhow::bail;
|
||||
use dioxus_dx_wire_format::BuildStage;
|
||||
|
||||
/// Run the project with the given arguments
|
||||
@@ -29,9 +30,11 @@ impl RunArgs {
|
||||
self.args.hot_reload = Some(false);
|
||||
self.args.watch = Some(false);
|
||||
|
||||
let mut builder = AppServer::start(self.args).await?;
|
||||
let mut builder = AppServer::new(self.args).await?;
|
||||
let mut devserver = WebServer::start(&builder)?;
|
||||
|
||||
builder.initialize();
|
||||
|
||||
loop {
|
||||
let msg = tokio::select! {
|
||||
msg = builder.wait() => msg,
|
||||
@@ -109,15 +112,11 @@ impl RunArgs {
|
||||
}
|
||||
BuildStage::Failed => {
|
||||
tracing::error!("[{platform}] Build failed");
|
||||
return Err(Error::Cargo(format!(
|
||||
"Build failed for platform: {platform}"
|
||||
)));
|
||||
bail!("Build failed for platform: {platform}");
|
||||
}
|
||||
BuildStage::Aborted => {
|
||||
tracing::error!("[{platform}] Build aborted");
|
||||
return Err(Error::Cargo(format!(
|
||||
"Build aborted for platform: {platform}"
|
||||
)));
|
||||
bail!("Build aborted for platform: {platform}");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -125,7 +124,7 @@ impl RunArgs {
|
||||
print!("{}", message);
|
||||
}
|
||||
BuilderUpdate::BuildFailed { err } => {
|
||||
tracing::error!("Build failed: {}", err);
|
||||
tracing::error!("❌ Build failed: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
BuilderUpdate::StdoutReceived { msg } => {
|
||||
@@ -139,9 +138,7 @@ impl RunArgs {
|
||||
tracing::error!(
|
||||
"Application [{platform}] exited with error: {status}"
|
||||
);
|
||||
return Err(Error::Runtime(format!(
|
||||
"Application [{platform}] exited with error: {status}"
|
||||
)));
|
||||
bail!("Application [{platform}] exited with error: {status}");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -181,7 +181,7 @@ impl ServeArgs {
|
||||
})
|
||||
.unwrap_or_else(|| format!("dx serve panicked: {as_str}"));
|
||||
|
||||
Err(crate::error::Error::CapturedPanic(message))
|
||||
Err(anyhow::anyhow!(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::{Result, StructuredOutput};
|
||||
use anyhow::bail;
|
||||
use dioxus_rsx::{BodyNode, CallBody, TemplateBody};
|
||||
|
||||
/// Translate some source file into Dioxus code
|
||||
@@ -108,7 +109,7 @@ fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String>
|
||||
|
||||
// Make sure not both are specified
|
||||
if file.is_some() && raw.is_some() {
|
||||
return Err("Only one of --file or --raw should be specified.".into());
|
||||
bail!("Only one of --file or --raw should be specified.");
|
||||
}
|
||||
|
||||
if let Some(raw) = raw {
|
||||
@@ -121,7 +122,7 @@ fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String>
|
||||
|
||||
// If neither exist, we try to read from stdin
|
||||
if std::io::stdin().is_terminal() {
|
||||
return Err(anyhow::anyhow!("No input file, source, or stdin to translate from.").into());
|
||||
bail!("No input file, source, or stdin to translate from.");
|
||||
}
|
||||
|
||||
let mut buffer = String::new();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::{Result, Workspace};
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use itertools::Itertools;
|
||||
use self_update::cargo_crate_version;
|
||||
|
||||
@@ -93,7 +93,7 @@ impl SelfUpdate {
|
||||
} else if cfg!(target_arch = "aarch64") {
|
||||
"aarch64"
|
||||
} else {
|
||||
return Err(Error::Unique("Unsupported architecture".to_string()));
|
||||
bail!("Unsupported architecture");
|
||||
};
|
||||
|
||||
let cur_os = if cfg!(target_os = "windows") {
|
||||
@@ -103,7 +103,7 @@ impl SelfUpdate {
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin"
|
||||
} else {
|
||||
return Err(Error::Unique("Unsupported OS".to_string()));
|
||||
bail!("Unsupported OS");
|
||||
};
|
||||
|
||||
let zip_ext = "zip";
|
||||
@@ -118,7 +118,7 @@ impl SelfUpdate {
|
||||
&& a.name.contains(cur_arch)
|
||||
&& a.name.ends_with(zip_ext)
|
||||
})
|
||||
.ok_or_else(|| Error::Unique("No suitable release found found".to_string()))?;
|
||||
.context("No suitable asset found")?;
|
||||
|
||||
let install_dir = Workspace::dioxus_home_dir().join("self-update");
|
||||
std::fs::create_dir_all(&install_dir).context("Failed to create install directory")?;
|
||||
@@ -161,10 +161,7 @@ impl SelfUpdate {
|
||||
|
||||
let executable = install_dir.join("dx");
|
||||
if !executable.exists() {
|
||||
return Err(Error::Unique(format!(
|
||||
"Executable not found in {}",
|
||||
install_dir.display()
|
||||
)));
|
||||
bail!("Executable not found in {}", install_dir.display());
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
|
||||
+25
-69
@@ -1,77 +1,33 @@
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
pub use anyhow::Error;
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(ThisError, Debug)]
|
||||
pub(crate) enum Error {
|
||||
/// Used when errors need to propagate but are too unique to be typed
|
||||
#[error("{0}")]
|
||||
Unique(String),
|
||||
pub fn log_stacktrace(err: &anyhow::Error, padding: usize) -> String {
|
||||
let mut trace = format!("{err}",);
|
||||
|
||||
#[error("I/O Error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("Format Error: {0}")]
|
||||
Format(#[from] std::fmt::Error),
|
||||
|
||||
#[error("Format failed: {0}")]
|
||||
Parse(String),
|
||||
|
||||
#[error("Runtime Error: {0}")]
|
||||
Runtime(String),
|
||||
|
||||
#[error("Cargo Error: {0}")]
|
||||
Cargo(String),
|
||||
|
||||
#[error("Invalid proxy URL: {0}")]
|
||||
InvalidProxy(#[from] hyper::http::uri::InvalidUri),
|
||||
|
||||
#[error("Establishing proxy: {0}")]
|
||||
ProxySetup(String),
|
||||
|
||||
#[error("Bundling project: {0}")]
|
||||
BundleFailed(#[from] tauri_bundler::Error),
|
||||
|
||||
#[error("Performing hotpatch: {0}")]
|
||||
PatchingFailed(#[from] crate::build::PatchError),
|
||||
|
||||
#[error("Reading object file: {0}")]
|
||||
ObjectReadFailed(#[from] object::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
CapturedPanic(String),
|
||||
|
||||
#[error("Rendering template error: {0}")]
|
||||
TemplateParse(#[from] handlebars::RenderError),
|
||||
|
||||
#[error("Network connectivity error: {0}")]
|
||||
Network(String),
|
||||
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(s: &str) -> Self {
|
||||
Error::Unique(s.to_string())
|
||||
for (idx, cause) in err.chain().enumerate().skip(1) {
|
||||
trace.push_str(&format!(
|
||||
"\n{}{IDX_STYLE}{idx}{IDX_STYLE:#}: {}",
|
||||
" ".repeat(padding),
|
||||
cause
|
||||
.to_string()
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(idx, line)| {
|
||||
if idx == 0 {
|
||||
line.to_string()
|
||||
} else {
|
||||
format!("{}{}", " ".repeat(padding + 3), line)
|
||||
}
|
||||
})
|
||||
.join("\n"),
|
||||
IDX_STYLE = crate::styles::GLOW_STYLE,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Self {
|
||||
Error::Unique(s)
|
||||
if crate::VERBOSITY.get().unwrap().trace {
|
||||
trace.push_str(&format!("\nBacktrace:\n{}", err.backtrace()));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<html_parser::Error> for Error {
|
||||
fn from(e: html_parser::Error) -> Self {
|
||||
Self::Parse(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hyper::Error> for Error {
|
||||
fn from(e: hyper::Error) -> Self {
|
||||
Self::Runtime(e.to_string())
|
||||
}
|
||||
trace
|
||||
}
|
||||
@@ -56,13 +56,13 @@ async fn main() {
|
||||
Commands::Config(opts) => opts.config().await,
|
||||
Commands::Autoformat(opts) => opts.autoformat().await,
|
||||
Commands::Check(opts) => opts.check().await,
|
||||
Commands::Clean(opts) => opts.clean().await,
|
||||
Commands::Build(opts) => opts.build().await,
|
||||
Commands::Serve(opts) => opts.serve().await,
|
||||
Commands::Bundle(opts) => opts.bundle().await,
|
||||
Commands::Run(opts) => opts.run().await,
|
||||
Commands::SelfUpdate(opts) => opts.self_update().await,
|
||||
Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await,
|
||||
Commands::Doctor(opts) => opts.doctor().await,
|
||||
};
|
||||
|
||||
// Provide a structured output for third party tools that can consume the output of the CLI
|
||||
@@ -71,17 +71,18 @@ async fn main() {
|
||||
tracing::debug!(json = ?output);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"{ERROR_STYLE}failed:{ERROR_STYLE:#} {err}",
|
||||
ERROR_STYLE = styles::ERROR_STYLE
|
||||
);
|
||||
|
||||
tracing::error!(
|
||||
json = ?StructuredOutput::Error {
|
||||
message: format!("{err:?}"),
|
||||
},
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"{ERROR_STYLE}Failed{ERROR_STYLE:#}: {}",
|
||||
crate::error::log_stacktrace(&err, 1),
|
||||
ERROR_STYLE = crate::styles::ERROR_STYLE,
|
||||
);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
styles::{GLOW_STYLE, LINK_STYLE},
|
||||
AppBuilder, BuildId, BuildMode, BuilderUpdate, Error, Platform, Result, ServeArgs,
|
||||
TraceController,
|
||||
AppBuilder, BuildId, BuildMode, BuilderUpdate, Platform, Result, ServeArgs, TraceController,
|
||||
};
|
||||
|
||||
mod ansi_buffer;
|
||||
@@ -12,6 +11,7 @@ mod runner;
|
||||
mod server;
|
||||
mod update;
|
||||
|
||||
use anyhow::bail;
|
||||
use dioxus_dx_wire_format::BuildStage;
|
||||
pub(crate) use output::*;
|
||||
pub(crate) use runner::*;
|
||||
@@ -41,7 +41,7 @@ pub(crate) use update::*;
|
||||
pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> Result<()> {
|
||||
// Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
|
||||
let exit_on_error = args.exit_on_error;
|
||||
let mut builder = AppServer::start(args).await?;
|
||||
let mut builder = AppServer::new(args).await?;
|
||||
let mut devserver = WebServer::start(&builder)?;
|
||||
let mut screen = Output::start(builder.interactive).await?;
|
||||
|
||||
@@ -66,6 +66,8 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
}
|
||||
);
|
||||
|
||||
builder.initialize();
|
||||
|
||||
loop {
|
||||
// Draw the state of the server to the screen
|
||||
screen.render(&builder, &devserver);
|
||||
@@ -145,18 +147,14 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
stage: BuildStage::Failed,
|
||||
} => {
|
||||
if exit_on_error {
|
||||
return Err(Error::Cargo(format!(
|
||||
"Build failed for platform: {platform}"
|
||||
)));
|
||||
bail!("Build failed for platform: {platform}");
|
||||
}
|
||||
}
|
||||
BuilderUpdate::Progress {
|
||||
stage: BuildStage::Aborted,
|
||||
} => {
|
||||
if exit_on_error {
|
||||
return Err(Error::Cargo(format!(
|
||||
"Build aborted for platform: {platform}"
|
||||
)));
|
||||
bail!("Build aborted for platform: {platform}");
|
||||
}
|
||||
}
|
||||
BuilderUpdate::Progress { .. } => {}
|
||||
@@ -164,7 +162,12 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
screen.push_cargo_log(message);
|
||||
}
|
||||
BuilderUpdate::BuildFailed { err } => {
|
||||
tracing::error!("Build failed: {}", err);
|
||||
tracing::error!(
|
||||
"{ERROR_STYLE}Build failed{ERROR_STYLE:#}: {}",
|
||||
crate::error::log_stacktrace(&err, 15),
|
||||
ERROR_STYLE = crate::styles::ERROR_STYLE,
|
||||
);
|
||||
|
||||
if exit_on_error {
|
||||
return Err(err);
|
||||
}
|
||||
@@ -184,7 +187,9 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to hot-patch app: {err}");
|
||||
|
||||
if matches!(err, crate::Error::PatchingFailed(_)) {
|
||||
if let Some(_patching) =
|
||||
err.downcast_ref::<crate::build::PatchError>()
|
||||
{
|
||||
tracing::info!("Starting full rebuild: {err}");
|
||||
builder.full_rebuild().await;
|
||||
devserver.send_reload_start().await;
|
||||
@@ -216,9 +221,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
} else {
|
||||
tracing::error!("Application [{platform}] exited with error: {status}");
|
||||
if exit_on_error {
|
||||
return Err(Error::Runtime(format!(
|
||||
"Application [{platform}] exited with error: {status}"
|
||||
)));
|
||||
bail!("Application [{platform}] exited with error: {status}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +251,10 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
}
|
||||
_ => {
|
||||
if let Err(err) = builder.open_all(&devserver, true).await {
|
||||
tracing::error!("Failed to open app: {err}")
|
||||
tracing::error!(
|
||||
"Failed to open app: {}",
|
||||
crate::error::log_stacktrace(&err, 15)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -278,7 +284,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
_ = devserver.shutdown().await;
|
||||
|
||||
match error {
|
||||
Some(err) => return Err(anyhow::anyhow!("{}", err).into()),
|
||||
Some(err) => return Err(err),
|
||||
None => return Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use tracing::Level;
|
||||
use super::AppServer;
|
||||
|
||||
const TICK_RATE_MS: u64 = 100;
|
||||
const VIEWPORT_MAX_WIDTH: u16 = 100;
|
||||
const VIEWPORT_MAX_WIDTH: u16 = 90;
|
||||
const VIEWPORT_HEIGHT_SMALL: u16 = 5;
|
||||
const VIEWPORT_HEIGHT_BIG: u16 = 13;
|
||||
|
||||
@@ -204,7 +204,7 @@ impl Output {
|
||||
Ok(Some(update)) => return update,
|
||||
Err(ee) => {
|
||||
return ServeUpdate::Exit {
|
||||
error: Some(Box::new(ee)),
|
||||
error: Some(anyhow::anyhow!(ee)),
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
@@ -675,7 +675,7 @@ impl Output {
|
||||
if client.build.platform == Platform::Web {
|
||||
"Serving at: ".gray()
|
||||
} else {
|
||||
"ServerFns at: ".gray()
|
||||
"Server at: ".gray()
|
||||
},
|
||||
address,
|
||||
])),
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::config::WebProxyConfig;
|
||||
use crate::TraceSrc;
|
||||
use crate::{Error, Result};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{bail, Context};
|
||||
use axum::body::Body;
|
||||
use axum::http::request::Parts;
|
||||
use axum::{body::Body as MyBody, response::IntoResponse};
|
||||
@@ -47,7 +47,7 @@ impl ProxyClient {
|
||||
self.inner
|
||||
.request(req)
|
||||
.await
|
||||
.map_err(|err| crate::error::Error::Other(anyhow!(err)))
|
||||
.context("Failed to send proxy request")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,11 @@ pub(crate) fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Ro
|
||||
let trimmed_path = path.trim_start_matches('/');
|
||||
|
||||
if trimmed_path.is_empty() {
|
||||
return Err(crate::Error::ProxySetup(format!(
|
||||
bail!(
|
||||
"Proxy backend URL must have a non-empty path, e.g. {}/api instead of {}",
|
||||
proxy.backend.trim_end_matches('/'),
|
||||
proxy.backend
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
let method_router = proxy_to(url, false, handle_proxy_error);
|
||||
@@ -268,21 +268,4 @@ mod test {
|
||||
async fn add_proxy_trailing_slash() {
|
||||
test_proxy_requests("/api/".to_string()).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_proxy_empty_path() {
|
||||
let config = WebProxyConfig {
|
||||
backend: "http://localhost:8000".to_string(),
|
||||
};
|
||||
let router = super::add_proxy(Router::new(), &config);
|
||||
match router.unwrap_err() {
|
||||
crate::Error::ProxySetup(e) => {
|
||||
assert_eq!(
|
||||
e,
|
||||
"Proxy backend URL must have a non-empty path, e.g. http://localhost:8000/api instead of http://localhost:8000"
|
||||
);
|
||||
}
|
||||
e => panic!("Unexpected error type: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::{AppBuilder, ServeUpdate, WebServer};
|
||||
use crate::{
|
||||
platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
|
||||
BuildTargets, BuilderUpdate, Error, HotpatchModuleCache, Platform, Result, ServeArgs,
|
||||
TailwindCli, TraceSrc, Workspace,
|
||||
BuildTargets, BuilderUpdate, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli,
|
||||
TraceSrc, Workspace,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use dioxus_core::internal::{
|
||||
HotReloadTemplateWithLocation, HotReloadedTemplate, TemplateGlobalKey,
|
||||
};
|
||||
@@ -92,7 +92,7 @@ pub(crate) struct CachedFile {
|
||||
|
||||
impl AppServer {
|
||||
/// Create the AppRunner and then initialize the filemap with the crate directory.
|
||||
pub(crate) async fn start(args: ServeArgs) -> Result<Self> {
|
||||
pub(crate) async fn new(args: ServeArgs) -> Result<Self> {
|
||||
let workspace = Workspace::current().await?;
|
||||
|
||||
// Resolve the simpler args
|
||||
@@ -115,7 +115,8 @@ impl AppServer {
|
||||
|
||||
let open_browser = args
|
||||
.open
|
||||
.unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or_default());
|
||||
.unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(true))
|
||||
&& interactive;
|
||||
|
||||
let wsl_file_poll_interval = args
|
||||
.wsl_file_poll_interval
|
||||
@@ -161,15 +162,9 @@ impl AppServer {
|
||||
|
||||
let watch_fs = args.watch.unwrap_or(true);
|
||||
let use_hotpatch_engine = args.hot_patch;
|
||||
let build_mode = match use_hotpatch_engine {
|
||||
true => BuildMode::Fat,
|
||||
false => BuildMode::Base,
|
||||
};
|
||||
|
||||
let client = AppBuilder::start(&client, build_mode.clone())?;
|
||||
let server = server
|
||||
.map(|server| AppBuilder::start(&server, build_mode))
|
||||
.transpose()?;
|
||||
let client = AppBuilder::new(&client)?;
|
||||
let server = server.map(|server| AppBuilder::new(&server)).transpose()?;
|
||||
|
||||
let tw_watcher = TailwindCli::serve(
|
||||
client.build.package_manifest_dir(),
|
||||
@@ -229,6 +224,18 @@ impl AppServer {
|
||||
Ok(runner)
|
||||
}
|
||||
|
||||
pub(crate) fn initialize(&mut self) {
|
||||
let build_mode = match self.use_hotpatch_engine {
|
||||
true => BuildMode::Fat,
|
||||
false => BuildMode::Base,
|
||||
};
|
||||
|
||||
self.client.start(build_mode.clone());
|
||||
if let Some(server) = self.server.as_mut() {
|
||||
server.start(build_mode);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn rebuild_ssg(&mut self, devserver: &WebServer) {
|
||||
if self.client.stage != BuildStage::Success {
|
||||
return;
|
||||
@@ -355,9 +362,10 @@ impl AppServer {
|
||||
for path in files {
|
||||
// for various assets that might be linked in, we just try to hotreloading them forcefully
|
||||
// That is, unless they appear in an include! macro, in which case we need to a full rebuild....
|
||||
let Some(ext) = path.extension().and_then(|v| v.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|v| v.to_str())
|
||||
.unwrap_or_default();
|
||||
|
||||
// If it's an asset, we want to hotreload it
|
||||
// todo(jon): don't hardcode this here
|
||||
@@ -458,6 +466,21 @@ impl AppServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it's not a rust file, then it might be depended on via include! or similar
|
||||
if ext != "rs" {
|
||||
if let Some(artifacts) = self.client.artifacts.as_ref() {
|
||||
if artifacts.depinfo.files.contains(path) {
|
||||
needs_full_rebuild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the client is in a failed state, any changes to rsx should trigger a rebuild/hotpatch
|
||||
if self.client.stage == BuildStage::Failed && !templates.is_empty() {
|
||||
needs_full_rebuild = true
|
||||
}
|
||||
|
||||
// todo - we need to distinguish between hotpatchable rebuilds and true full rebuilds.
|
||||
@@ -685,7 +708,7 @@ impl AppServer {
|
||||
.hotpatch(res, cache)
|
||||
.await
|
||||
}
|
||||
_ => return Err(Error::Runtime("Invalid build id".into())),
|
||||
_ => bail!("Invalid build id"),
|
||||
}?;
|
||||
|
||||
if id == BuildId::CLIENT {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, Platform,
|
||||
Result, TraceSrc,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{
|
||||
@@ -635,8 +635,7 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||
{
|
||||
return Ok((cert, key));
|
||||
} else {
|
||||
// missing cert or key
|
||||
return Err("https is enabled but cert or key path is missing".into());
|
||||
bail!("https is enabled but cert or key path is missing");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,7 +681,7 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||
tracing::error!(dx_src = ?TraceSrc::Dev, "An error occurred while generating mkcert certificates: {}", e.to_string())
|
||||
}
|
||||
};
|
||||
return Err("failed to generate mkcert certificates".into());
|
||||
bail!("failed to generate mkcert certificates");
|
||||
}
|
||||
Ok(mut cmd) => {
|
||||
cmd.wait().await?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{BuildId, BuilderUpdate, Platform, TraceMsg};
|
||||
use crate::{BuildId, BuilderUpdate, Error, Platform, TraceMsg};
|
||||
use axum::extract::ws::Message as WsMessage;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -44,6 +44,6 @@ pub(crate) enum ServeUpdate {
|
||||
},
|
||||
|
||||
Exit {
|
||||
error: Option<Box<dyn std::error::Error + Send + Sync>>,
|
||||
error: Option<Error>,
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{Result, TraceSrc};
|
||||
use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::LazyLock;
|
||||
use std::{fs, path::PathBuf, sync::Arc};
|
||||
@@ -89,16 +90,14 @@ impl CliSettings {
|
||||
?path,
|
||||
"failed to create directories for settings file"
|
||||
);
|
||||
return Err(
|
||||
anyhow::anyhow!("failed to create directories for settings file: {e}").into(),
|
||||
);
|
||||
bail!("failed to create directories for settings file: {e}");
|
||||
}
|
||||
|
||||
// Write the data.
|
||||
let result = fs::write(&path, data.clone());
|
||||
if let Err(e) = result {
|
||||
error!(?data, ?path, "failed to save global cli settings");
|
||||
return Err(anyhow::anyhow!("failed to save global cli settings: {e}").into());
|
||||
bail!("failed to save global cli settings: {e}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -141,6 +141,7 @@ impl TailwindCli {
|
||||
.arg("--output")
|
||||
.arg(output_path)
|
||||
.args(watch.then_some("--watch"))
|
||||
.current_dir(manifest_dir)
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
@@ -150,7 +151,7 @@ impl TailwindCli {
|
||||
Ok(proc)
|
||||
}
|
||||
|
||||
fn get_binary_path(&self) -> anyhow::Result<PathBuf> {
|
||||
pub fn get_binary_path(&self) -> anyhow::Result<PathBuf> {
|
||||
if CliSettings::prefer_no_downloads() {
|
||||
which::which("tailwindcss").map_err(|_| anyhow!("Missing tailwindcss@{}", self.version))
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,7 @@ use flate2::read::GzDecoder;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tar::Archive;
|
||||
use tempfile::TempDir;
|
||||
use tokio::{fs, process::Command};
|
||||
use tokio::process::Command;
|
||||
|
||||
pub(crate) struct WasmBindgen {
|
||||
version: String,
|
||||
@@ -100,7 +100,7 @@ impl WasmBindgen {
|
||||
|
||||
/// Run the bindgen command with the current settings
|
||||
pub(crate) async fn run(&self) -> Result<std::process::Output> {
|
||||
let binary = self.get_binary_path().await?;
|
||||
let binary = self.get_binary_path()?;
|
||||
|
||||
let mut args = Vec::new();
|
||||
|
||||
@@ -253,7 +253,7 @@ impl WasmBindgen {
|
||||
})?;
|
||||
|
||||
// Get the final binary location.
|
||||
let binary_path = self.get_binary_path().await?;
|
||||
let binary_path = self.get_binary_path()?;
|
||||
|
||||
// Download then extract wasm-bindgen-cli.
|
||||
let bytes = reqwest::get(url).await?.bytes().await?;
|
||||
@@ -300,11 +300,10 @@ impl WasmBindgen {
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
fs::copy(
|
||||
std::fs::copy(
|
||||
tempdir.path().join(self.downloaded_bin_name()),
|
||||
self.get_binary_path().await?,
|
||||
)
|
||||
.await?;
|
||||
self.get_binary_path()?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -336,11 +335,10 @@ impl WasmBindgen {
|
||||
tracing::info!("Copying into path: {}", tempdir.path().display());
|
||||
|
||||
// copy the wasm-bindgen out of the tempdir to the final location
|
||||
fs::copy(
|
||||
std::fs::copy(
|
||||
tempdir.path().join("bin").join(self.downloaded_bin_name()),
|
||||
self.get_binary_path().await?,
|
||||
self.get_binary_path()?,
|
||||
)
|
||||
.await
|
||||
.context("failed to copy wasm-bindgen binary")?;
|
||||
|
||||
Ok(())
|
||||
@@ -352,7 +350,7 @@ impl WasmBindgen {
|
||||
self.version
|
||||
);
|
||||
|
||||
let binary = self.get_binary_path().await?;
|
||||
let binary = self.get_binary_path()?;
|
||||
let output = Command::new(binary)
|
||||
.args(["--version"])
|
||||
.output()
|
||||
@@ -381,7 +379,7 @@ impl WasmBindgen {
|
||||
);
|
||||
|
||||
let binary_name = self.installed_bin_name();
|
||||
let path = self.install_dir().await?.join(binary_name);
|
||||
let path = self.install_dir()?.join(binary_name);
|
||||
|
||||
if !path.exists() {
|
||||
self.install().await?;
|
||||
@@ -390,20 +388,20 @@ impl WasmBindgen {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_binary_path(&self) -> anyhow::Result<PathBuf> {
|
||||
pub fn get_binary_path(&self) -> anyhow::Result<PathBuf> {
|
||||
if CliSettings::prefer_no_downloads() {
|
||||
which::which("wasm-bindgen")
|
||||
.map_err(|_| anyhow!("Missing wasm-bindgen-cli@{}", self.version))
|
||||
} else {
|
||||
let installed_name = self.installed_bin_name();
|
||||
let install_dir = self.install_dir().await?;
|
||||
let install_dir = self.install_dir()?;
|
||||
Ok(install_dir.join(installed_name))
|
||||
}
|
||||
}
|
||||
|
||||
async fn install_dir(&self) -> anyhow::Result<PathBuf> {
|
||||
fn install_dir(&self) -> anyhow::Result<PathBuf> {
|
||||
let bindgen_dir = Workspace::dioxus_home_dir().join("wasm-bindgen/");
|
||||
fs::create_dir_all(&bindgen_dir).await?;
|
||||
std::fs::create_dir_all(&bindgen_dir)?;
|
||||
Ok(bindgen_dir)
|
||||
}
|
||||
|
||||
@@ -501,7 +499,7 @@ mod test {
|
||||
|
||||
/// Helper to test that the installed binary actually exists.
|
||||
async fn verify_installation(binary: &WasmBindgen) {
|
||||
let path = binary.install_dir().await.unwrap();
|
||||
let path = binary.install_dir().unwrap();
|
||||
let name = binary.installed_bin_name();
|
||||
let binary_path = path.join(name);
|
||||
assert!(
|
||||
@@ -513,9 +511,9 @@ mod test {
|
||||
/// Delete the installed binary. The temp folder should be automatically deleted.
|
||||
async fn reset_test() {
|
||||
let binary = WasmBindgen::new(VERSION);
|
||||
let path = binary.install_dir().await.unwrap();
|
||||
let path = binary.install_dir().unwrap();
|
||||
let name = binary.installed_bin_name();
|
||||
let binary_path = path.join(name);
|
||||
let _ = tokio::fs::remove_file(binary_path).await;
|
||||
let _ = std::fs::remove_file(binary_path);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::config::WasmOptLevel;
|
||||
use crate::{CliSettings, Result, WasmOptConfig, Workspace};
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use flate2::read::GzDecoder;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tar::Archive;
|
||||
@@ -16,8 +16,11 @@ pub async fn write_wasm(bytes: &[u8], output_path: &Path, cfg: &WasmOptConfig) -
|
||||
pub async fn optimize(input_path: &Path, output_path: &Path, cfg: &WasmOptConfig) -> Result<()> {
|
||||
let wasm_opt = WasmOpt::new(input_path, output_path, cfg)
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!("Failed to create wasm-opt instance: {}", err))?;
|
||||
wasm_opt.optimize().await?;
|
||||
.context("Failed to create wasm-opt instance")?;
|
||||
wasm_opt
|
||||
.optimize()
|
||||
.await
|
||||
.context("Failed to run wasm-opt")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -170,9 +173,7 @@ async fn find_latest_wasm_opt_download_url() -> anyhow::Result<String> {
|
||||
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
|
||||
"arm64-macos"
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unknown platform for wasm-opt installation. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH."
|
||||
));
|
||||
bail!("Unknown platform for wasm-opt installation. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.");
|
||||
};
|
||||
|
||||
// Find the first asset with a name that contains the platform string
|
||||
@@ -184,12 +185,10 @@ async fn find_latest_wasm_opt_download_url() -> anyhow::Result<String> {
|
||||
.and_then(|name| name.as_str())
|
||||
.is_some_and(|name| name.contains(platform) && !name.ends_with("sha256"))
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"No suitable wasm-opt binary found for platform: {}. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.",
|
||||
platform
|
||||
)
|
||||
})?;
|
||||
.with_context(|| anyhow!(
|
||||
"No suitable wasm-opt binary found for platform: {}. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.",
|
||||
platform
|
||||
))?;
|
||||
|
||||
// Extract the download URL from the asset
|
||||
let download_url = asset
|
||||
@@ -201,7 +200,7 @@ async fn find_latest_wasm_opt_download_url() -> anyhow::Result<String> {
|
||||
}
|
||||
|
||||
/// Get the path to the wasm-opt binary, downloading it if necessary
|
||||
async fn get_binary_path() -> anyhow::Result<PathBuf> {
|
||||
pub async fn get_binary_path() -> anyhow::Result<PathBuf> {
|
||||
let install_dir = install_dir();
|
||||
let install_path = installed_bin_path(&install_dir);
|
||||
|
||||
@@ -224,6 +223,25 @@ async fn get_binary_path() -> anyhow::Result<PathBuf> {
|
||||
Ok(install_path)
|
||||
}
|
||||
|
||||
pub fn installed_location() -> Option<PathBuf> {
|
||||
let install_dir = install_dir();
|
||||
let install_path = installed_bin_path(&install_dir);
|
||||
|
||||
if install_path.exists() {
|
||||
return Some(install_path);
|
||||
}
|
||||
|
||||
if CliSettings::prefer_no_downloads() {
|
||||
if let Ok(existing) = which::which("wasm-opt") {
|
||||
return Some(existing);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn install_dir() -> PathBuf {
|
||||
Workspace::dioxus_home_dir().join("binaryen")
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::styles::GLOW_STYLE;
|
||||
use crate::CliSettings;
|
||||
use crate::Result;
|
||||
use crate::{config::DioxusConfig, AndroidTools};
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Context};
|
||||
use ignore::gitignore::Gitignore;
|
||||
use krates::{semver::Version, KrateDetails, LockOptions};
|
||||
use krates::{Cmd, Krates, NodeId};
|
||||
@@ -21,6 +21,7 @@ pub struct Workspace {
|
||||
pub(crate) ignore: Gitignore,
|
||||
pub(crate) cargo_toml: cargo_toml::Manifest,
|
||||
pub(crate) android_tools: Option<Arc<AndroidTools>>,
|
||||
pub(crate) xcode: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
@@ -48,9 +49,7 @@ impl Workspace {
|
||||
|
||||
let mut builder = krates::Builder::new();
|
||||
builder.workspace(true);
|
||||
let res = builder
|
||||
.build(cmd, |_| {})
|
||||
.context("Failed to run cargo metadata");
|
||||
let res = builder.build(cmd, |_| {})?;
|
||||
|
||||
if !lock_options.offline {
|
||||
if let Ok(res) = std::env::var("SIMULATE_SLOW_NETWORK") {
|
||||
@@ -58,7 +57,7 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
Ok(res) as Result<Krates, krates::Error>
|
||||
});
|
||||
|
||||
let spin_future = async move {
|
||||
@@ -76,10 +75,14 @@ impl Workspace {
|
||||
};
|
||||
|
||||
let krates = tokio::select! {
|
||||
f = krates_future => f.context("failed to run cargo metadata")??,
|
||||
_ = spin_future => return Err(crate::Error::Network(
|
||||
"cargo metadata took too long to respond, try again with --offline".to_string(),
|
||||
)),
|
||||
f = krates_future => {
|
||||
let res = f?;
|
||||
if let Err(krates::Error::Metadata(e)) = res {
|
||||
bail!("{e}");
|
||||
}
|
||||
res?
|
||||
},
|
||||
_ = spin_future => bail!("cargo metadata took too long to respond, try again with --offline"),
|
||||
};
|
||||
|
||||
let settings = CliSettings::global_or_default();
|
||||
@@ -107,6 +110,13 @@ impl Workspace {
|
||||
|
||||
let android_tools = crate::build::get_android_tools();
|
||||
|
||||
let xcode = Command::new("xcode-select")
|
||||
.arg("-p")
|
||||
.output()
|
||||
.await
|
||||
.ok()
|
||||
.map(|s| String::from_utf8_lossy(&s.stdout).trim().to_string().into());
|
||||
|
||||
let workspace = Arc::new(Self {
|
||||
krates,
|
||||
settings,
|
||||
@@ -116,6 +126,7 @@ impl Workspace {
|
||||
ignore,
|
||||
cargo_toml,
|
||||
android_tools,
|
||||
xcode,
|
||||
});
|
||||
|
||||
tracing::debug!(
|
||||
@@ -141,10 +152,10 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn android_tools(&self) -> Result<Arc<AndroidTools>> {
|
||||
Ok(self
|
||||
self
|
||||
.android_tools
|
||||
.clone()
|
||||
.context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")?)
|
||||
.context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")
|
||||
}
|
||||
|
||||
pub fn is_release_profile(&self, profile: &str) -> bool {
|
||||
@@ -195,7 +206,10 @@ impl Workspace {
|
||||
let max = dioxus_versions.iter().max().unwrap();
|
||||
|
||||
// If the minimum dioxus version is greater than the current cli version, warn the user
|
||||
if min > &dx_semver || max < &dx_semver {
|
||||
if min > &dx_semver
|
||||
|| max < &dx_semver
|
||||
|| dioxus_versions.iter().any(|f| f.pre != dx_semver.pre)
|
||||
{
|
||||
tracing::error!(
|
||||
r#"🚫dx and dioxus versions are incompatible!
|
||||
• dx version: {dx_semver}
|
||||
@@ -249,6 +263,14 @@ impl Workspace {
|
||||
self.gcc_ld_dir().join("wasm-ld")
|
||||
}
|
||||
|
||||
/// Return the version of the wasm-bindgen crate if it exists
|
||||
pub fn wasm_bindgen_version(&self) -> Option<String> {
|
||||
self.krates
|
||||
.krates_by_name("wasm-bindgen")
|
||||
.next()
|
||||
.map(|krate| krate.krate.version.to_string())
|
||||
}
|
||||
|
||||
// wasm-ld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/wasm-ld
|
||||
// rust-lld: ./rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rust-lld
|
||||
fn gcc_ld_dir(&self) -> PathBuf {
|
||||
@@ -377,7 +399,7 @@ impl Workspace {
|
||||
|
||||
toml::from_str::<DioxusConfig>(&std::fs::read_to_string(&dioxus_conf_file)?)
|
||||
.map_err(|err| {
|
||||
anyhow::anyhow!("Failed to parse Dioxus.toml at {dioxus_conf_file:?}: {err}").into()
|
||||
anyhow::anyhow!("Failed to parse Dioxus.toml at {dioxus_conf_file:?}: {err}")
|
||||
})
|
||||
.map(Some)
|
||||
}
|
||||
@@ -459,24 +481,18 @@ impl Workspace {
|
||||
}
|
||||
None
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
crate::Error::Cargo("Failed to find directory containing Cargo.toml".to_string())
|
||||
})
|
||||
.context("Failed to find directory containing Cargo.toml")
|
||||
}
|
||||
|
||||
/// Returns the properly canonicalized path to the dx executable, used for linking and wrapping rustc
|
||||
pub(crate) fn path_to_dx() -> Result<PathBuf> {
|
||||
Ok(
|
||||
dunce::canonicalize(std::env::current_exe().context("Failed to find dx")?)
|
||||
.context("Failed to find dx")?,
|
||||
)
|
||||
dunce::canonicalize(std::env::current_exe().context("Failed to find dx")?)
|
||||
.context("Failed to find dx")
|
||||
}
|
||||
|
||||
/// Returns the path to the dioxus home directory, used to install tools and other things
|
||||
pub(crate) fn dioxus_home_dir() -> PathBuf {
|
||||
dirs::data_local_dir()
|
||||
.map(|f| f.join("dioxus/"))
|
||||
.unwrap_or_else(|| dirs::home_dir().unwrap().join(".dioxus"))
|
||||
dirs::home_dir().unwrap().join(".dioxus")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user