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:
Jonathan Kelley authored and GitHub committed 2025-07-14 12:54:15 -07:00
1 parent 97947b169b
commit 727752a965
78 files changed
+2270 -817

No files matched your search

+3 -4
View File
@@ -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());
);
}
}
+67 -29
View File
@@ -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(())
+1 -1
View File
@@ -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
View File
@@ -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(())
}
}
+40
View File
@@ -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> {