feat: copy frameworks to Frameworks folder, detect linkers, custom target dirs (#4174)

* feat: copy frameworks to Frameworks folder

* add dylib linking to patching

* remove logging

* rename __aslr_offset

* fix android/linux

* support linker flavor

* support custom target dirs

* custom cargo target dir support

* whoops ios

* dlsym doesn't exist on windows

* look for the right main symbol per platform

* distinction between mains

* export main on windows

* enbable rlib caching

* add dx hash

* adjust linking warnings

* update tui with hotreload state

* implement symbol flags

* add flags

* only run ranlib on darwin

* enable lld

* paths

* add harness test for tls

* wip: revert launch mobile

* wip

* fix fullstack mobile

* fix a few bugs
- long paths?
- reverting back to original

* pass-thru fuse-ld args

* fix nan in render, fix empty archives

* consider existing dep files if archive exists

* fmt

* drop msrv
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-05-24 22:41:30 +01:00
1 parent 751f1920c3
commit 0982c1ff8b
25 files changed
+1044 -315

No files matched your search

+1
View File
@@ -138,6 +138,7 @@ wasm-bindgen-externref-xform = "0.2.100"
pdb = "0.8.0"
self_update = { version = "0.42.0", features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate"] }
self-replace = "1.5.0"
cargo-config2 = { workspace = true }
[build-dependencies]
built = { version = "0.7.5", features = ["git2"] }
+2 -2
View File
@@ -208,12 +208,12 @@ impl AppBuilder {
self.bundling_progress = 0.0;
}
BuildStage::Starting { crate_count, .. } => {
self.expected_crates = *crate_count;
self.expected_crates = *crate_count.max(&1);
}
BuildStage::InstallingTooling => {}
BuildStage::Compiling { current, total, .. } => {
self.compiled_crates = *current;
self.expected_crates = *total;
self.expected_crates = *total.max(&1);
if self.compile_start.is_none() {
self.compile_start = Some(Instant::now());
+70 -40
View File
@@ -3,8 +3,8 @@ use itertools::Itertools;
use object::{
macho::{self},
read::File,
write::{MachOBuildVersion, StandardSection, Symbol, SymbolSection},
Endianness, Object, ObjectSymbol, SymbolKind, SymbolScope,
write::{MachOBuildVersion, SectionId, StandardSection, Symbol, SymbolId, SymbolSection},
Endianness, Object, ObjectSymbol, SymbolFlags, SymbolKind, SymbolScope,
};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use std::{
@@ -84,6 +84,7 @@ pub struct CachedSymbol {
pub is_undefined: bool,
pub is_weak: bool,
pub size: u64,
pub flags: SymbolFlags<SectionId, SymbolId>,
}
impl PartialEq for HotpatchModuleCache {
@@ -139,6 +140,7 @@ impl HotpatchModuleCache {
is_undefined,
is_weak: false,
size: 0,
flags: SymbolFlags::None,
},
);
}
@@ -158,6 +160,7 @@ impl HotpatchModuleCache {
is_undefined,
is_weak: false,
size: 0,
flags: SymbolFlags::None,
},
);
}
@@ -252,6 +255,15 @@ impl HotpatchModuleCache {
let symbol_table = obj
.symbols()
.filter_map(|s| {
let flags = match s.flags() {
SymbolFlags::None => SymbolFlags::None,
SymbolFlags::Elf { st_info, st_other } => {
SymbolFlags::Elf { st_info, st_other }
}
SymbolFlags::MachO { n_desc } => SymbolFlags::MachO { n_desc },
_ => SymbolFlags::None,
};
Some((
s.name().ok()?.to_string(),
CachedSymbol {
@@ -260,6 +272,7 @@ impl HotpatchModuleCache {
is_weak: s.is_weak(),
kind: s.kind(),
size: s.size(),
flags,
},
))
})
@@ -326,9 +339,9 @@ fn create_windows_jump_table(patch: &Path, cache: &HotpatchModuleCache) -> Resul
.context("failed to find 'main' symbol in patch")?;
let aslr_reference = old_name_to_addr
.get("__aslr_reference")
.get("main")
.map(|s| s.address)
.context("failed to find '_aslr_reference' symbol in original module")?;
.context("failed to find '_main' symbol in original module")?;
Ok(JumpTable {
lib: patch.to_path_buf(),
@@ -371,31 +384,15 @@ fn create_native_jump_table(
}
}
let new_base_address = match triple.operating_system {
// The symbol in the symtab is called "_main" but in the dysymtab it is called "main"
OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => {
*new_name_to_addr
.get("_main")
.context("failed to find '_main' symbol in patch")?
}
// No distincation between the two on these platforms
OperatingSystem::Freebsd
| OperatingSystem::Openbsd
| OperatingSystem::Linux
| OperatingSystem::Windows => *new_name_to_addr
.get("main")
.context("failed to find 'main' symbol in patch")?,
// On wasm, it doesn't matter what the address is since the binary is PIC
_ => 0,
};
let sentinel = main_sentinel(triple);
let new_base_address = new_name_to_addr
.get(sentinel)
.cloned()
.context("failed to find 'main' symbol in base - are deubg symbols enabled?")?;
let aslr_reference = old_name_to_addr
.get("___aslr_reference")
.or_else(|| old_name_to_addr.get("__aslr_reference"))
.get(sentinel)
.map(|s| s.address)
.context("failed to find '___aslr_reference' symbol in original module")?;
.context("failed to find 'main' symbol in original module - are debug symbols enabled?")?;
Ok(JumpTable {
lib: patch.to_path_buf(),
@@ -831,22 +828,31 @@ pub fn create_undefined_symbol_stub(
_ => {}
}
let symbol_table = &cache.symbol_table;
// Get the offset from the main module and adjust the addresses by the slide;
let aslr_ref_address = cache
.symbol_table
.get(main_sentinel(triple))
.context("failed to find '_main' symbol in patch")?
.address;
if aslr_reference < aslr_ref_address {
return Err(PatchError::InvalidModule(
format!(
"ASLR reference is less than the main module's address - is there a `main`?. {:x} < {:x}", aslr_reference, aslr_ref_address )
));
}
// Get the offset from the main module and adjust the addresses by the slide
let aslr_ref_address = symbol_table
.get("___aslr_reference")
.or_else(|| symbol_table.get("__aslr_reference"))
.map(|s| s.address)
.context("Failed to find ___aslr_reference symbol")?;
let aslr_offset = aslr_reference - aslr_ref_address;
// we need to assemble a PLT/GOT so direct calls to the patch symbols work
// for each symbol we either write the address directly (as a symbol) or create a PLT/GOT entry
let text_section = obj.section_id(StandardSection::Text);
for name in undefined_symbols {
let Some(sym) = symbol_table.get(name.as_str().trim_start_matches("__imp_")) else {
tracing::error!("Symbol not found: {}", name);
let Some(sym) = cache
.symbol_table
.get(name.as_str().trim_start_matches("__imp_"))
else {
tracing::debug!("Symbol not found: {}", name);
continue;
};
@@ -899,7 +905,7 @@ pub fn create_undefined_symbol_stub(
kind: SymbolKind::Data, // Always Data for IAT entries
weak: false,
section: SymbolSection::Section(data_section),
flags: object::SymbolFlags::None,
flags: SymbolFlags::None,
});
}
@@ -1028,7 +1034,7 @@ pub fn create_undefined_symbol_stub(
kind: SymbolKind::Text,
weak: false,
section: SymbolSection::Section(text_section),
flags: object::SymbolFlags::None,
flags: SymbolFlags::None, // ignore for these stubs
});
}
@@ -1097,7 +1103,7 @@ pub fn create_undefined_symbol_stub(
kind: SymbolKind::Tls,
weak: false,
section: SymbolSection::Section(tls_section),
flags: object::SymbolFlags::None,
flags: SymbolFlags::None, // ignore for these stubs
});
}
@@ -1108,6 +1114,15 @@ pub fn create_undefined_symbol_stub(
SymbolKind::Unknown => SymbolKind::Data,
k => k,
};
// plain linux *wants* these flags, but android doesn't.
// unsure what's going on here, but this is special cased for now.
// I think the more advanced linkers don't want these flags, but the default linux linker (ld) does.
let flags = match triple.environment {
target_lexicon::Environment::Android => SymbolFlags::None,
_ => sym.flags,
};
obj.add_symbol(Symbol {
name: name.as_bytes()[name_offset..].to_vec(),
value: abs_addr,
@@ -1116,7 +1131,7 @@ pub fn create_undefined_symbol_stub(
kind,
weak: sym.is_weak,
section: SymbolSection::Absolute,
flags: object::SymbolFlags::None,
flags,
});
}
}
@@ -1439,3 +1454,18 @@ fn parse_module_with_ids(bindgened: &[u8]) -> Result<ParsedModule> {
symbols,
})
}
/// Get the main sentinel symbol for the given target triple
///
/// We need to special case darwin since `main` is the entrypoint but `_main` is the actual symbol.
/// The entrypoint ends up outside the text section, seemingly, and breaks our aslr detection.
fn main_sentinel(triple: &Triple) -> &'static str {
match triple.operating_system {
// The symbol in the symtab is called "_main" but in the dysymtab it is called "main"
OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => {
"_main"
}
_ => "main",
}
}
+319 -115
View File
@@ -316,8 +316,9 @@
//! - xbuild: <https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs>
use crate::{
AndroidTools, BuildContext, DioxusConfig, Error, LinkAction, Platform, Result, RustcArgs,
TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR,
AndroidTools, BuildContext, DioxusConfig, Error, LinkAction, LinkerFlavor, Platform, Result,
RustcArgs, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig, Workspace,
DX_RUSTC_WRAPPER_ENV_VAR,
};
use anyhow::Context;
use cargo_metadata::diagnostic::Diagnostic;
@@ -377,7 +378,7 @@ pub(crate) struct BuildRequest {
pub(crate) extra_cargo_args: Vec<String>,
pub(crate) extra_rustc_args: Vec<String>,
pub(crate) no_default_features: bool,
pub(crate) custom_target_dir: Option<PathBuf>,
pub(crate) target_dir: PathBuf,
pub(crate) skip_assets: bool,
pub(crate) wasm_split: bool,
pub(crate) debug_symbols: bool,
@@ -643,12 +644,26 @@ impl BuildRequest {
},
};
let custom_linker = if platform == Platform::Android {
// Somethings we override are also present in the user's config.
// If we can't get them by introspecting cargo, then we need to get them from the config
let cargo_config = cargo_config2::Config::load().unwrap();
let mut custom_linker = if platform == Platform::Android {
Some(workspace.android_tools()?.android_cc(&triple))
} else {
None
};
if let Ok(Some(linker)) = cargo_config.linker(triple.to_string()) {
custom_linker = Some(linker);
}
let target_dir = std::env::var("CARGO_TARGET_DIR")
.ok()
.map(PathBuf::from)
.or_else(|| cargo_config.build.target_dir.clone())
.unwrap_or_else(|| workspace.workspace_root().join("target"));
// Set up some tempfiles so we can do some IPC between us and the linker/rustc wrapper (which is occasionally us!)
let link_args_file = Arc::new(
NamedTempFile::with_suffix(".txt")
@@ -677,11 +692,16 @@ impl BuildRequest {
• link_args_file: {},
• link_err_file: {},
• rustc_wrapper_args_file: {},
• session_cache_dir: {}"#,
• session_cache_dir: {}
• linker: {:?}
• target_dir: {:?}
"#,
link_args_file.path().display(),
link_err_file.path().display(),
rustc_wrapper_args_file.path().display(),
session_cache_dir.path().display(),
custom_linker,
target_dir,
);
Ok(Self {
@@ -696,7 +716,7 @@ impl BuildRequest {
workspace,
config,
enabled_platforms,
custom_target_dir: None,
target_dir,
custom_linker,
link_args_file,
link_err_file,
@@ -739,6 +759,9 @@ impl BuildRequest {
self.write_executable(ctx, &artifacts.exe, &mut artifacts.assets)
.await
.context("Failed to write main 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")?;
@@ -874,8 +897,6 @@ impl BuildRequest {
}
}
let exe = output_location.context("Cargo build failed - no output location. Toggle tracing mode (press `t`) for more information.")?;
// Accumulate the rustc args from the wrapper, if they exist and can be parsed.
let mut direct_rustc = RustcArgs::default();
if let Ok(res) = std::fs::read_to_string(self.rustc_wrapper_args_file.path()) {
@@ -887,13 +908,34 @@ impl BuildRequest {
// If there's any warnings from the linker, we should print them out
if let Ok(linker_warnings) = std::fs::read_to_string(self.link_err_file.path()) {
if !linker_warnings.is_empty() {
tracing::warn!("Linker warnings: {}", linker_warnings);
if output_location.is_none() {
tracing::error!("Linker warnings: {}", linker_warnings);
} else {
tracing::debug!("Linker warnings: {}", linker_warnings);
}
}
}
// Collect the linker args from the and update the rustc args
direct_rustc.link_args = std::fs::read_to_string(self.link_args_file.path())
.context("Failed to read link args from file")?
.lines()
.map(|s| s.to_string())
.collect::<Vec<_>>();
let exe = output_location.context("Cargo build failed - no output location. Toggle tracing mode (press `t`) for more information.")?;
// Fat builds need to be linked with the fat linker. Would also like to link here for thin builds
if matches!(ctx.mode, BuildMode::Fat) {
let link_start = SystemTime::now();
self.run_fat_link(ctx, &exe, &direct_rustc).await?;
tracing::debug!(
"Fat linking completed in {}us",
SystemTime::now()
.duration_since(link_start)
.unwrap()
.as_micros()
);
}
let assets = self.collect_assets(&exe, ctx)?;
@@ -901,7 +943,11 @@ impl BuildRequest {
let mode = ctx.mode.clone();
let platform = self.platform;
tracing::debug!("Build completed successfully: {:?}", exe);
tracing::debug!(
"Build completed successfully in {}us: {:?}",
time_end.duration_since(time_start).unwrap().as_micros(),
exe
);
Ok(BuildArtifacts {
time_end,
@@ -991,12 +1037,6 @@ impl BuildRequest {
| Platform::Ios
| Platform::Liveview
| Platform::Server => {
// We wipe away the dir completely, which is not great behavior :/
// Don't wipe server since web will need this folder too.
if self.platform != Platform::Server {
_ = std::fs::remove_dir_all(self.exe_dir());
}
std::fs::create_dir_all(self.exe_dir())?;
std::fs::copy(exe, self.main_exe())?;
}
@@ -1005,6 +1045,52 @@ impl BuildRequest {
Ok(())
}
async fn write_frameworks(&self, _ctx: &BuildContext, direct_rustc: &RustcArgs) -> Result<()> {
let framework_dir = self.frameworks_folder();
_ = std::fs::create_dir_all(&framework_dir);
for arg in &direct_rustc.link_args {
// 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
if arg.ends_with(".dylib") | arg.ends_with(".so") {
let from = PathBuf::from(arg);
let to = framework_dir.join(from.file_name().unwrap());
_ = std::fs::remove_file(&to);
tracing::debug!("Copying framework from {from:?} to {to:?}");
// in dev and on normal oses, we want to symlink the file
// otherwise, just copy it (since in release you want to distribute the framework)
if cfg!(any(windows, unix)) && !self.release {
#[cfg(windows)]
std::os::windows::fs::symlink_file(from, to).with_context(|| {
"Failed to symlink framework into bundle: {from:?} -> {to:?}"
})?;
#[cfg(unix)]
std::os::unix::fs::symlink(from, to).with_context(|| {
"Failed to symlink framework into bundle: {from:?} -> {to:?}"
})?;
} else {
std::fs::copy(from, to)?;
}
}
}
Ok(())
}
fn frameworks_folder(&self) -> PathBuf {
match self.triple.operating_system {
OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) => {
self.root_dir().join("Contents").join("Frameworks")
}
OperatingSystem::IOS(_) => self.root_dir().join("Frameworks"),
OperatingSystem::Linux | OperatingSystem::Windows => self.root_dir(),
_ => self.root_dir(),
}
}
/// Copy the assets out of the manifest and into the target location
///
/// Should be the same on all platforms - just copy over the assets from the manifest into the output directory
@@ -1200,6 +1286,7 @@ impl BuildRequest {
// -nodefaultlibs
// -Wl,-all_load
// ```
let mut dylibs = vec![];
let mut object_files = args
.iter()
.filter(|arg| arg.ends_with(".rcgu.o"))
@@ -1233,6 +1320,15 @@ impl BuildRequest {
let patch_file = self.main_exe().with_file_name("stub.o");
std::fs::write(&patch_file, stub_bytes)?;
object_files.push(patch_file);
// Add the dylibs/sos to the linker args
// Make sure to use the one in the bundle, not the ones in the target dir or system.
for arg in &rustc_args.link_args {
if arg.ends_with(".dylib") || arg.ends_with(".so") {
let path = PathBuf::from(arg);
dylibs.push(self.frameworks_folder().join(path.file_name().unwrap()));
}
}
}
// And now we can run the linker with our new args
@@ -1251,6 +1347,7 @@ impl BuildRequest {
// 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)
.env_clear()
@@ -1298,12 +1395,9 @@ impl BuildRequest {
/// This is basically just stripping away the rlibs and other libraries that will be satisfied
/// by our stub step.
fn thin_link_args(&self, original_args: &[&str]) -> Result<Vec<String>> {
use target_lexicon::OperatingSystem;
let triple = self.triple.clone();
let mut out_args = vec![];
match triple.operating_system {
match self.linker_flavor() {
// wasm32-unknown-unknown -> use wasm-ld (gnu-lld)
//
// We need to import a few things - namely the memory and ifunc table.
@@ -1323,7 +1417,7 @@ impl BuildRequest {
// I think we can make relocation-model=pic work for non-wasm platforms, enabling
// fully relocatable modules with no host coordination in lieu of sending out
// the aslr slide at runtime.
OperatingSystem::Unknown if self.platform == Platform::Web => {
LinkerFlavor::WasmLld => {
out_args.extend([
"--fatal-warnings".to_string(),
"--verbose".to_string(),
@@ -1344,7 +1438,7 @@ impl BuildRequest {
//
// Most importantly, we want to pass `-dylib` to both CC and the linker to indicate that
// we want to generate the shared library instead of an executable.
OperatingSystem::IOS(_) | OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) => {
LinkerFlavor::Darwin => {
out_args.extend(["-Wl,-dylib".to_string()]);
// Preserve the original args. We only preserve:
@@ -1367,7 +1461,7 @@ impl BuildRequest {
// android/linux need to be compatible with lld
//
// android currently drags along its own libraries and other zany flags
OperatingSystem::Linux => {
LinkerFlavor::Gnu => {
out_args.extend([
"-shared".to_string(),
"-Wl,--eh-frame-hdr".to_string(),
@@ -1391,13 +1485,14 @@ impl BuildRequest {
if arg.starts_with("-l")
|| arg.starts_with("-m")
|| arg.starts_with("-Wl,--target=")
|| arg.starts_with("-Wl,-fuse-ld")
{
out_args.push(arg.to_string());
}
}
}
OperatingSystem::Windows => {
LinkerFlavor::Msvc => {
out_args.extend([
"shlwapi.lib".to_string(),
"kernel32.lib".to_string(),
@@ -1415,7 +1510,9 @@ impl BuildRequest {
]);
}
_ => return Err(anyhow::anyhow!("Unsupported platform for thin linking").into()),
LinkerFlavor::Unsupported => {
return Err(anyhow::anyhow!("Unsupported platform for thin linking").into())
}
}
let extract_value = |arg: &str| -> Option<String> {
@@ -1460,15 +1557,12 @@ impl BuildRequest {
.unwrap_or(0),
));
let extension = match self.triple.operating_system {
OperatingSystem::Darwin(_) => "dylib",
OperatingSystem::MacOSX(_) => "dylib",
OperatingSystem::IOS(_) => "dylib",
OperatingSystem::Windows => "dll",
OperatingSystem::Linux => "so",
OperatingSystem::Wasi => "wasm",
OperatingSystem::Unknown if self.platform == Platform::Web => "wasm",
_ => "",
let extension = match self.linker_flavor() {
LinkerFlavor::Darwin => "dylib",
LinkerFlavor::Gnu => "so",
LinkerFlavor::WasmLld => "wasm",
LinkerFlavor::Msvc => "dll",
LinkerFlavor::Unsupported => "",
};
path.with_extension(extension)
@@ -1512,38 +1606,65 @@ impl BuildRequest {
) -> Result<()> {
ctx.status_starting_link();
let raw_args = std::fs::read_to_string(self.link_args_file.path())
.context("Failed to read link args from file")?;
let args = raw_args.lines().collect::<Vec<_>>();
// Filter out the rlib files from the arguments
let rlibs = args
let rlibs = rustc_args
.link_args
.iter()
.filter(|arg| arg.ends_with(".rlib"))
.map(PathBuf::from)
.collect::<Vec<_>>();
// Acquire a hash from the rlib names
// Acquire a hash from the rlib names, sizes, modified times, and dx's git commit hash
// This ensures that any changes in dx or the rlibs will cause a new hash to be generated
// The hash relies on both dx and rustc hashes, so it should be thoroughly unique. Keep it
// short to avoid long file names.
let hash_id = Uuid::new_v5(
&Uuid::NAMESPACE_OID,
rlibs
.iter()
.map(|p| p.file_name().unwrap().to_string_lossy())
.map(|p| {
format!(
"{}-{}-{}-{}",
p.file_name().unwrap().to_string_lossy(),
p.metadata().map(|m| m.len()).unwrap_or_default(),
p.metadata()
.ok()
.and_then(|m| m.modified().ok())
.and_then(|f| f.duration_since(UNIX_EPOCH).map(|f| f.as_secs()).ok())
.unwrap_or_default(),
crate::dx_build_info::GIT_COMMIT_HASH.unwrap_or_default()
)
})
.collect::<String>()
.as_bytes(),
);
)
.to_string()
.chars()
.take(8)
.collect::<String>();
// Check if we already have a cached object file
let out_ar_path = exe.with_file_name(format!("libfatdependencies-{hash_id}.a"));
let out_ar_path = exe.with_file_name(format!("libdeps-{hash_id}.a",));
let out_rlibs_list = exe.with_file_name(format!("rlibs-{hash_id}.txt"));
let mut archive_has_contents = out_ar_path.exists();
let mut compiler_rlibs = vec![];
// Use the rlibs list if it exists
let mut compiler_rlibs = std::fs::read_to_string(&out_rlibs_list)
.ok()
.map(|s| s.lines().map(PathBuf::from).collect::<Vec<_>>())
.unwrap_or_default();
// Create it by dumping all the rlibs into it
// This will include the std rlibs too, which can severely bloat the size of the archive
//
// The nature of this process involves making extremely fat archives, so we should try and
// speed up the future linking process by caching the archive.
if !crate::devcfg::should_cache_dep_lib(&out_ar_path) {
//
// Since we're using the git hash for the CLI entropy, debug builds should always regenerate
// the archive since their hash might not change, but the logic might.
if !archive_has_contents || cfg!(debug_assertions) {
compiler_rlibs.clear();
let mut bytes = vec![];
let mut out_ar = ar::Builder::new(&mut bytes);
for rlib in &rlibs {
@@ -1573,7 +1694,11 @@ impl BuildRequest {
}
// rlibs might contain dlls/sos/lib files which we don't want to include
if name.ends_with(".dll") || name.ends_with(".so") || name.ends_with(".lib") {
if name.ends_with(".dll")
|| name.ends_with(".so")
|| name.ends_with(".lib")
|| name.ends_with(".dylib")
{
compiler_rlibs.push(rlib.to_owned());
continue;
}
@@ -1582,6 +1707,7 @@ impl BuildRequest {
tracing::debug!("Unknown object file in rlib: {:?}", name);
}
archive_has_contents = true;
out_ar
.append(&object_file.header().clone(), object_file)
.context("Failed to add object file to archive")?;
@@ -1595,8 +1721,10 @@ impl BuildRequest {
// Run the ranlib command to index the archive. This slows down this process a bit,
// but is necessary for some linkers to work properly.
// We ignore its error in case it doesn't recognize the architecture
if let Some(ranlib) = self.select_ranlib() {
_ = Command::new(ranlib).arg(&out_ar_path).output().await;
if self.linker_flavor() == LinkerFlavor::Darwin {
if let Some(ranlib) = self.select_ranlib() {
_ = Command::new(ranlib).arg(&out_ar_path).output().await;
}
}
}
@@ -1606,62 +1734,71 @@ 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 = args.iter().map(|s| s.to_string()).collect::<Vec<_>>();
if let Some(first_rlib) = args.iter().position(|arg| arg.ends_with(".rlib")) {
match self.triple.operating_system {
OperatingSystem::Unknown if self.platform == Platform::Web => {
// We need to use the --whole-archive flag for wasm
args[first_rlib] = "--whole-archive".to_string();
args.insert(first_rlib + 1, out_ar_path.display().to_string());
args.insert(first_rlib + 2, "--no-whole-archive".to_string());
args.retain(|arg| !arg.ends_with(".rlib"));
// add back the compiler rlibs
for rlib in compiler_rlibs.iter().rev() {
args.insert(first_rlib + 3, rlib.display().to_string());
let mut args = rustc_args.link_args.clone();
if let Some(last_object) = args.iter().rposition(|arg| arg.ends_with(".o")) {
if archive_has_contents {
match self.linker_flavor() {
LinkerFlavor::WasmLld => {
args.insert(last_object, "--whole-archive".to_string());
args.insert(last_object + 1, out_ar_path.display().to_string());
args.insert(last_object + 2, "--no-whole-archive".to_string());
args.retain(|arg| !arg.ends_with(".rlib"));
for rlib in compiler_rlibs.iter().rev() {
args.insert(last_object + 3, rlib.display().to_string());
}
}
}
// Subtle difference - on linux and android we go through clang and thus pass `-Wl,` prefix
OperatingSystem::Linux => {
args[first_rlib] = "-Wl,--whole-archive".to_string();
args.insert(first_rlib + 1, out_ar_path.display().to_string());
args.insert(first_rlib + 2, "-Wl,--no-whole-archive".to_string());
args.retain(|arg| !arg.ends_with(".rlib"));
// add back the compiler rlibs
for rlib in compiler_rlibs.iter().rev() {
args.insert(first_rlib + 3, rlib.display().to_string());
LinkerFlavor::Gnu => {
args.insert(last_object, "-Wl,--whole-archive".to_string());
args.insert(last_object + 1, out_ar_path.display().to_string());
args.insert(last_object + 2, "-Wl,--no-whole-archive".to_string());
args.retain(|arg| !arg.ends_with(".rlib"));
for rlib in compiler_rlibs.iter().rev() {
args.insert(last_object + 3, rlib.display().to_string());
}
}
}
OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => {
args[first_rlib] = "-Wl,-force_load".to_string();
args.insert(first_rlib + 1, out_ar_path.display().to_string());
args.retain(|arg| !arg.ends_with(".rlib"));
// add back the compiler rlibs
for rlib in compiler_rlibs.iter().rev() {
args.insert(first_rlib + 2, rlib.display().to_string());
LinkerFlavor::Darwin => {
args.insert(last_object, "-Wl,-force_load".to_string());
args.insert(last_object + 1, out_ar_path.display().to_string());
args.retain(|arg| !arg.ends_with(".rlib"));
for rlib in compiler_rlibs.iter().rev() {
args.insert(last_object + 2, rlib.display().to_string());
}
}
args.insert(first_rlib + 3, "-Wl,-all_load".to_string());
}
OperatingSystem::Windows => {
args[first_rlib] = format!("/WHOLEARCHIVE:{}", out_ar_path.display());
args.retain(|arg| !arg.ends_with(".rlib"));
// add back the compiler rlibs
for rlib in compiler_rlibs.iter().rev() {
args.insert(first_rlib + 1, rlib.display().to_string());
LinkerFlavor::Msvc => {
args.insert(
last_object,
format!("/WHOLEARCHIVE:{}", out_ar_path.display()),
);
args.retain(|arg| !arg.ends_with(".rlib"));
for rlib in compiler_rlibs.iter().rev() {
args.insert(last_object + 1, rlib.display().to_string());
}
}
LinkerFlavor::Unsupported => {
tracing::error!("Unsupported platform for fat linking");
}
};
}
}
args.insert(first_rlib, "/HIGHENTROPYVA:NO".to_string());
}
// Add custom args to the linkers
match self.linker_flavor() {
LinkerFlavor::Gnu => {
// Export `main` so subsecond can use it for a reference point
args.push("-Wl,--export-dynamic-symbol,main".to_string());
}
LinkerFlavor::Darwin => {
// `-all_load` is an extra step to ensure that all symbols are loaded (different than force_load)
args.push("-Wl,-all_load".to_string());
}
LinkerFlavor::Msvc => {
// Prevent alsr from overflowing 32 bits
args.push("/HIGHENTROPYVA:NO".to_string());
_ => {}
};
// Export `main` so subsecond can use it for a reference point
args.push("/EXPORT:main".to_string());
}
LinkerFlavor::WasmLld | LinkerFlavor::Unsupported => {}
}
// We also need to remove the `-o` flag since we want the linker output to end up in the
@@ -1690,6 +1827,7 @@ impl BuildRequest {
let linker = self.select_linker()?;
tracing::trace!("Fat linking with args: {:?} {:#?}", linker, args);
tracing::trace!("Fat linking with env: {:#?}", rustc_args.envs);
// Run the linker directly!
let out_arg = match self.triple.operating_system {
@@ -1724,9 +1862,64 @@ impl BuildRequest {
_ = std::fs::remove_file(f);
}
// Cache the rlibs list
_ = std::fs::write(
&out_rlibs_list,
compiler_rlibs
.into_iter()
.map(|s| s.display().to_string())
.join("\n"),
);
Ok(())
}
/// Automatically detect the linker flavor based on the target triple and any custom linkers.
///
/// This tries to replicate what rustc does when selecting the linker flavor based on the linker
/// and triple.
fn linker_flavor(&self) -> LinkerFlavor {
if let Some(custom) = self.custom_linker.as_ref() {
let name = custom.file_name().unwrap().to_ascii_lowercase();
match name.to_str() {
Some("lld-link") => return LinkerFlavor::Msvc,
Some("lld-link.exe") => return LinkerFlavor::Msvc,
Some("wasm-ld") => return LinkerFlavor::WasmLld,
Some("ld64.lld") => return LinkerFlavor::Darwin,
Some("ld.lld") => return LinkerFlavor::Gnu,
Some("ld.gold") => return LinkerFlavor::Gnu,
Some("mold") => return LinkerFlavor::Gnu,
Some("sold") => return LinkerFlavor::Gnu,
Some("wild") => return LinkerFlavor::Gnu,
_ => {}
}
}
match self.triple.environment {
target_lexicon::Environment::Gnu
| target_lexicon::Environment::Gnuabi64
| target_lexicon::Environment::Gnueabi
| target_lexicon::Environment::Gnueabihf
| target_lexicon::Environment::GnuLlvm => LinkerFlavor::Gnu,
target_lexicon::Environment::Musl => LinkerFlavor::Gnu,
target_lexicon::Environment::Android => LinkerFlavor::Gnu,
target_lexicon::Environment::Msvc => LinkerFlavor::Msvc,
target_lexicon::Environment::Macabi => LinkerFlavor::Darwin,
_ => match self.triple.operating_system {
OperatingSystem::Darwin(_) => LinkerFlavor::Darwin,
OperatingSystem::IOS(_) => LinkerFlavor::Darwin,
OperatingSystem::MacOSX(_) => LinkerFlavor::Darwin,
OperatingSystem::Linux => LinkerFlavor::Gnu,
OperatingSystem::Windows => LinkerFlavor::Msvc,
_ => match self.triple.architecture {
target_lexicon::Architecture::Wasm32 => LinkerFlavor::WasmLld,
target_lexicon::Architecture::Wasm64 => LinkerFlavor::WasmLld,
_ => LinkerFlavor::Unsupported,
},
},
}
}
/// Select the linker to use for this platform.
///
/// We prefer to use the rust-lld linker when we can since it's usually there.
@@ -1750,25 +1943,25 @@ impl BuildRequest {
return Ok(PathBuf::from(linker));
}
let cc = match self.triple.operating_system {
OperatingSystem::Unknown if self.platform == Platform::Web => self.workspace.wasm_ld(),
if let Some(linker) = self.custom_linker.clone() {
return Ok(linker);
}
// The android clang linker is *special* and has some android-specific flags that we need
//
// Note that this is *clang*, not `lld`.
OperatingSystem::Linux if self.platform == Platform::Android => {
self.workspace.android_tools()?.android_cc(&self.triple)
}
let cc = match self.linker_flavor() {
LinkerFlavor::WasmLld => self.workspace.wasm_ld(),
// On macOS, we use the system linker since it's usually there.
// We could also use `lld` here, but it might not be installed by default.
//
// Note that this is *clang*, not `lld`.
OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => self.workspace.cc(),
LinkerFlavor::Darwin => self.workspace.cc(),
// On Linux, we use the system linker since it's usually there.
LinkerFlavor::Gnu => self.workspace.cc(),
// On windows, instead of trying to find the system linker, we just go with the lld.link
// that rustup provides. It's faster and more stable then reyling on link.exe in path.
OperatingSystem::Windows => self.workspace.lld_link(),
LinkerFlavor::Msvc => self.workspace.lld_link(),
// The rest of the platforms use `cc` as the linker which should be available in your path,
// provided you have build-tools setup. On mac/linux this is the default, but on Windows
@@ -1782,7 +1975,7 @@ impl BuildRequest {
// Note that "cc" is *not* a linker. It's a compiler! The arguments we pass need to be in
// the form of `-Wl,<args>` for them to make it to the linker. This matches how rust does it
// which is confusing.
_ => self.workspace.cc(),
LinkerFlavor::Unsupported => self.workspace.cc(),
};
Ok(cc)
@@ -1827,7 +2020,7 @@ impl BuildRequest {
cmd.envs(rustc_args.envs.iter().cloned());
tracing::trace!("Setting env vars: {:#?}", rustc_args.envs);
// tracing::trace!("Setting env vars: {:#?}", rustc_args.envs);
Ok(cmd)
}
@@ -1935,6 +2128,21 @@ impl BuildRequest {
));
}
// Handle frameworks/dylibs by setting the rpath
// This is dependent on the bundle structure - in this case, appimage and appbundle for mac/linux
// todo: we need to figure out what to do for windows
match self.triple.operating_system {
OperatingSystem::Darwin(_) | OperatingSystem::IOS(_) => {
cargo_args.push("-Clink-arg=-Wl,-rpath,@executable_path/../Frameworks".to_string());
cargo_args.push("-Clink-arg=-Wl,-rpath,@executable_path".to_string());
}
OperatingSystem::Linux => {
cargo_args.push("-Clink-arg=-Wl,-rpath,$ORIGIN/../lib".to_string());
cargo_args.push("-Clink-arg=-Wl,-rpath,$ORIGIN".to_string());
}
_ => {}
}
// Our fancy hot-patching engine needs a lot of customization to work properly.
//
// These args are mostly intended to be passed when *fat* linking but are generally fine to
@@ -2051,10 +2259,6 @@ impl BuildRequest {
}));
}
if let Some(target_dir) = self.custom_target_dir.as_ref() {
env_vars.push(("CARGO_TARGET_DIR", target_dir.display().to_string()));
}
// If this is a release build, bake the base path and title into the binary with env vars.
// todo: should we even be doing this? might be better being a build.rs or something else.
if self.release {
@@ -2510,7 +2714,7 @@ impl BuildRequest {
/// is "distributed" after building an application (configurable in the
/// `Dioxus.toml`).
fn internal_out_dir(&self) -> PathBuf {
let dir = self.workspace_dir().join("target").join("dx");
let dir = self.target_dir.join("dx");
std::fs::create_dir_all(&dir).unwrap();
dir
}
+1 -1
View File
@@ -44,8 +44,8 @@ pub enum LinkerFlavor {
Gnu,
Darwin,
WasmLld,
Unix,
Msvc,
Unsupported, // a catch-all for unsupported linkers, usually the stripped-down unix ones
}
impl LinkAction {
-10
View File
@@ -1,15 +1,5 @@
//! Configuration of the CLI at runtime to enable certain experimental features.
use std::path::Path;
/// Should we cache the dependency library?
///
/// When the `DIOXUS_CACHE_DEP_LIB` environment variable is set, we will cache the dependency library
/// built from the target's dependencies.
pub(crate) fn should_cache_dep_lib(lib: &Path) -> bool {
std::env::var("DIOXUS_CACHE_DEP_LIB").is_ok() && lib.exists()
}
/// Should we force the entropy to be used on the main exe?
///
/// This is used to verify that binaries are copied with different names such that they don't collide
+2
View File
@@ -25,6 +25,7 @@ pub fn is_wrapping_rustc() -> bool {
pub struct RustcArgs {
pub args: Vec<String>,
pub envs: Vec<(String, String)>,
pub link_args: Vec<String>,
}
/// Run rustc directly, but output the result to a file.
@@ -50,6 +51,7 @@ pub async fn run_rustc() {
let rustc_args = RustcArgs {
args: args().skip(1).collect::<Vec<_>>(),
envs: vars().collect::<_>(),
link_args: Default::default(),
};
std::fs::create_dir_all(var_file.parent().expect("Failed to get parent dir"))
+7 -1
View File
@@ -721,7 +721,13 @@ impl Output {
frame.render_widget(
Paragraph::new(Line::from(vec![
"Hotreload: ".gray(),
"rsx and assets".yellow(),
if !state.runner.automatic_rebuilds {
"disabled".dark_gray()
} else if state.runner.use_hotpatch_engine {
"hot-patching".yellow()
} else {
"rsx and assets".yellow()
},
])),
meta_list[3],
);
+4 -3
View File
@@ -138,9 +138,10 @@ pub(crate) fn proxy_to(
match res {
Ok(res) => {
// log assets at a different log level
if uri.path().starts_with("/assets")
|| uri.path().starts_with("/_dioxus")
|| uri.path().starts_with("/public")
if uri.path().starts_with("/assets/")
|| uri.path().starts_with("/_dioxus/")
|| uri.path().starts_with("/public/")
|| uri.path().starts_with("/wasm/")
{
tracing::trace!(dx_src = ?TraceSrc::Dev, "[{}] {}", res.status().as_u16(), uri);
} else {
+3 -3
View File
@@ -341,15 +341,15 @@ impl AppServer {
continue;
};
// Update the most recent version of the file, so when we force a rebuild, we keep operating on the most recent version
cached_file.most_recent = Some(new_contents);
// This assumes the two files are structured similarly. If they're not, we can't diff them
let Some(changed_rsx) = dioxus_rsx_hotreload::diff_rsx(&new_file, &old_file) else {
needs_full_rebuild = true;
break;
};
// Update the most recent version of the file, so when we force a rebuild, we keep operating on the most recent version
cached_file.most_recent = Some(new_contents);
for ChangedRsx { old, new } in changed_rsx {
let old_start = old.span().start();