From ed53d2f920eea06c92f6c5da66aadf76a16ac6ac Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 24 Feb 2025 21:40:09 -0800 Subject: [PATCH 001/301] wip: inline object diffing logic --- Cargo.lock | 16 +- packages/cli/Cargo.toml | 6 +- packages/cli/src/build/builder.rs | 4 +- packages/cli/src/build/mod.rs | 2 + packages/cli/src/build/patch.rs | 635 ++++++++++++++++++ packages/cli/src/build/request.rs | 116 ++-- packages/cli/src/cli/link.rs | 53 +- packages/cli/src/cli/serve.rs | 4 + packages/cli/src/{config.rs => config/mod.rs} | 0 packages/cli/src/serve/mod.rs | 59 +- packages/cli/src/serve/runner.rs | 10 +- packages/core/src/any_props.rs | 2 +- packages/core/src/diff/component.rs | 4 +- packages/core/src/diff/node.rs | 2 + packages/core/src/properties.rs | 18 +- 15 files changed, 774 insertions(+), 157 deletions(-) create mode 100644 packages/cli/src/build/patch.rs rename packages/cli/src/{config.rs => config/mod.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index bb482827a2..ff935a7a92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3527,12 +3527,13 @@ dependencies = [ "hyper-util", "ignore", "include_dir", - "itertools 0.13.0", + "itertools 0.14.0", "krates", "local-ip-address", "log", "manganis", "manganis-core", + "memmap", "memoize", "notify", "object 0.36.5", @@ -7960,6 +7961,16 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "memmap2" version = "0.9.5" @@ -8756,7 +8767,10 @@ version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ + "crc32fast", "flate2", + "hashbrown 0.15.2", + "indexmap 2.7.0", "memchr", "ruzstd 0.7.3", "wasmparser 0.218.0", diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 5549e53066..43afefe310 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -111,9 +111,9 @@ manganis = { workspace = true } manganis-core = { workspace = true } # Extracting data from an executable -object = {version="0.36.0", features=["wasm"]} +object = { version = "0.36.0", features = ["all"] } tokio-util = { version = "0.7.11", features = ["full"] } -itertools = "0.13.0" +itertools = "0.14.0" throbber-widgets-tui = "=0.7.0" unicode-segmentation = "1.12.0" handlebars = "6.1.0" @@ -129,6 +129,8 @@ dircpy = "0.3.19" plist = "1.7.0" memoize = "0.5.1" +memmap = "0.7.0" + [build-dependencies] built = { version = "=0.7.4", features = ["git2"] } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 68e90c9548..f25ed25201 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -39,7 +39,7 @@ impl Builder { /// Create a new builder and immediately start a build pub(crate) fn start(krate: &DioxusCrate, args: BuildArgs) -> Result { let (tx, rx) = futures_channel::mpsc::unbounded(); - let request = BuildRequest::new(krate.clone(), args, tx.clone()); + let request = BuildRequest::new(krate.clone(), args, tx.clone(), None); Ok(Self { krate: krate.clone(), @@ -183,7 +183,7 @@ impl Builder { self.abort_all(); // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task - let request = BuildRequest::new(self.krate.clone(), args, self.tx.clone()); + let request = BuildRequest::new(self.krate.clone(), args, self.tx.clone(), None); self.request = request.clone(); self.stage = BuildStage::Restarting; diff --git a/packages/cli/src/build/mod.rs b/packages/cli/src/build/mod.rs index 56d9eb40b6..e3b05e8479 100644 --- a/packages/cli/src/build/mod.rs +++ b/packages/cli/src/build/mod.rs @@ -7,6 +7,7 @@ mod builder; mod bundle; +mod patch; mod prerender; mod progress; mod request; @@ -16,5 +17,6 @@ mod web; pub(crate) use builder::*; pub(crate) use bundle::*; +pub(crate) use patch::*; pub(crate) use progress::*; pub(crate) use request::*; diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs new file mode 100644 index 0000000000..3729effe15 --- /dev/null +++ b/packages/cli/src/build/patch.rs @@ -0,0 +1,635 @@ +use anyhow::{Context, Result}; +use itertools::Itertools; +use memmap::{Mmap, MmapOptions}; +use object::{ + read::File, Architecture, BinaryFormat, Endianness, Object, ObjectSection, ObjectSymbol, + Relocation, RelocationTarget, SectionIndex, +}; +use std::{cmp::Ordering, ffi::OsStr, fs, ops::Deref, path::PathBuf}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + path::Path, +}; +use tokio::process::Command; + +pub enum ReloadKind { + /// An RSX-only patch + Rsx, + + /// A patch that includes both RSX and binary assets + Binary, + + /// A full rebuild + Full, +} + +#[derive(Debug, Clone)] +pub struct PatchData { + pub direct_rustc: Vec, +} + +struct ObjectDiff { + old: BTreeMap, + new: BTreeMap, + modified_files: HashMap>, + modified_symbols: HashSet, + parents: HashMap>, +} + +impl ObjectDiff { + fn new() -> Result { + Ok(Self { + old: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-old"))?, + new: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-new"))?, + modified_files: Default::default(), + modified_symbols: Default::default(), + parents: Default::default(), + }) + } + + fn load(&mut self) -> Result<()> { + let num_right = self.new.len(); + + let keys = self.new.keys().cloned().collect::>(); + for (idx, f) in keys.iter().enumerate() { + println!("----- {:?} {}/{} -----", f, idx, num_right); + + let changed_before = self.modified_symbols.len(); + self.load_file(f)?; + let changed_after = self.modified_symbols.len(); + + if changed_after > changed_before { + println!("❌ -> {}", changed_after - changed_before); + } + } + + Ok(()) + } + + /// Walk the call to find the path to the main function + fn find_path_to_main(&self, name: &str) -> Vec { + let mut path = Vec::new(); + let mut visited = std::collections::HashSet::new(); + + // Helper function for DFS with backtracking + fn dfs( + current: &str, + path: &mut Vec, + visited: &mut std::collections::HashSet, + parents: &std::collections::HashMap>, + ) -> bool { + // If we've found main, we're done + if current.ends_with("_main") { + path.push(current.to_string()); + return true; + } + + // Mark current node as visited + visited.insert(current.to_string()); + path.push(current.to_string()); + + // Check all parents of the current node + if let Some(parent_nodes) = parents.get(current) { + for parent in parent_nodes { + if !visited.contains(parent) { + if dfs(parent, path, visited, parents) { + return true; + } + } + } + } + + // If no path is found through this node, backtrack + path.pop(); + + false + } + + // Start DFS from the given name + dfs(name, &mut path, &mut visited, &self.parents); + + path + } + + fn load_file(&mut self, name: &str) -> Result<()> { + let new = &self.new[name]; + let Some(old) = self.old.get(name) else { + self.modified_files.entry(new.path.clone()).or_default(); + return Ok(()); + }; + + let mut changed_list = HashSet::new(); + for section in new.file.sections() { + let n = section.name().unwrap(); + if n == "__text" + || n == "__const" + || n.starts_with("__literal") + || n == "__eh_frame" + || n == "__compact_unwind" + || n == "__gcc_except_tab" + || n == "__common" + || n == "__bss" + { + changed_list.extend(self.accumulate_changed(&old, &new, section.index())); + } else { + println!("Skipping section: {n}"); + } + } + + for c in changed_list.iter() { + if !c.starts_with("l") && !c.starts_with("ltmp") { + self.modified_symbols.insert(c.to_string()); + } else { + let mod_name = format!("{c}_{name}"); + self.modified_symbols.insert(mod_name); + } + } + + for (child, parents) in new.parents.iter() { + let child_name = match child.starts_with("l") { + true => format!("{child}_{name}"), + false => child.to_string(), + }; + + for parent in parents { + let p_name = match parent.starts_with("l") { + true => format!("{parent}_{name}"), + false => parent.to_string(), + }; + + self.parents + .entry(child_name.clone()) + .or_default() + .insert(p_name); + } + } + + Ok(()) + } + + fn accumulate_changed( + &self, + old: &LoadedFile, + new: &LoadedFile, + section_idx: SectionIndex, + ) -> HashSet<&'static str> { + let mut local_modified = HashSet::new(); + + // Accumulate modified symbols using masking in functions + let relocated_new = acc_symbols(&new.file, section_idx); + let mut relocated_old = acc_symbols(&old.file, section_idx) + .into_iter() + .map(|f| (f.name, f)) + .collect::>(); + + for right in relocated_new { + let Some(left) = relocated_old.remove(right.name) else { + local_modified.insert(right.name); + continue; + }; + + // If the contents of the assembly changed, track it + if !compare_masked(old.file, new.file, &left, &right) { + local_modified.insert(left.name); + local_modified.insert(right.name); + } + } + + local_modified + } +} + +/// A file loaded into memory with its analysis +/// +/// We leak the module to make it easier to deal with its contents +struct LoadedFile { + path: PathBuf, + open_file: std::fs::File, + mmap: &'static Mmap, + + file: &'static File<'static>, + + // symbol -> symbols + parents: HashMap<&'static str, HashSet<&'static str>>, +} + +impl LoadedFile { + fn from_dir(dir: &Path) -> anyhow::Result> { + std::fs::read_dir(dir)? + .into_iter() + .flatten() + .filter(|e| e.path().extension() == Some(OsStr::new("o"))) + .map(|e| { + Ok(( + e.path().file_name().unwrap().to_string_lossy().to_string(), + Self::new(e.path())?, + )) + }) + .collect() + } + + fn new(path: PathBuf) -> anyhow::Result { + let open_file = std::fs::File::open(&path)?; + let mmap = unsafe { MmapOptions::new().map(&open_file).unwrap() }; + let mmap: &'static Mmap = Box::leak(Box::new(mmap)); + let f = File::parse(mmap.deref() as &[u8])?; + let file: &'static File<'static> = Box::leak(Box::new(f)); + + // Set up the data structures + let mut sym_tab = HashMap::<&'static str, RelocatedSymbol<'static>>::new(); + let mut parents = HashMap::<&'static str, HashSet<&'static str>>::new(); + + // Build the symbol table + for sect in file.sections() { + for r in acc_symbols(&file, sect.index()) { + sym_tab.insert(r.name, r); + } + } + + // Create a map of address -> symbol so we can resolve the section of a symbol + let local_defs = file + .symbols() + .filter(|s| s.is_definition()) + .map(|s| (s.address(), s.name().unwrap())) + .collect::>(); + + // Build the call graph by walking the relocations + // We keep track of what calls whata + for (sym_name, sym) in sym_tab.iter() { + let sym_section = file.section_by_index(sym.section).unwrap(); + let sym_data = sym_section.data().unwrap(); + + for (addr, reloc) in sym.relocations.iter() { + let target = match symbol_name_of_relo(file, reloc.target()) { + Some(name) => name, + None => { + let addend = u64::from_le_bytes( + sym_data[*addr as usize..(*addr + 8) as usize] + .try_into() + .unwrap(), + ); + local_defs.get(&addend).unwrap() + } + }; + + parents.entry(target).or_default().insert(sym_name); + } + } + + Ok(Self { + path, + open_file, + mmap, + file, + parents, + }) + } +} + +/// A function with its relevant relocations to be used for masked comparisons +struct RelocatedSymbol<'a> { + name: &'a str, + /// offset within the section + offset: usize, + data: &'a [u8], + relocations: &'a [(u64, Relocation)], + sym: object::Symbol<'a, 'a>, + section: SectionIndex, +} + +fn acc_symbols<'a>(new: &'a File<'a>, section_idx: SectionIndex) -> Vec> { + let mut syms = vec![]; + + let section = new.section_by_index(section_idx).unwrap(); + + let sorted = new + .symbols() + .filter(|s| s.section_index() == Some(section_idx)) + .sorted_by(|a, b| { + let addr = a.address().cmp(&b.address()); + if addr == Ordering::Equal { + a.index().0.cmp(&b.index().0) + } else { + addr + } + }) + .collect::>(); + + // todo!!!!!! jon: don't leak this lol + let relocations = section + .relocations() + .sorted_by(|a, b| a.0.cmp(&b.0).reverse()) + .collect::>() + .leak(); + + let data = section.data().unwrap(); + + // No symbols, no symbols, + if sorted.is_empty() { + return vec![]; + } + + // The end of the currently analyzed function + let mut func_end = section.size() as usize; + + // The idx into the relocation list that applies to this function. We'll march these + let mut reloc_idx = 0; + + // Walk in reverse so we can use the text_length as the initial backstop and to match relocation order + for sym in sorted.into_iter().rev() { + let sym_offset = sym.address() - section.address(); + + // Move the head/tail to include the sub-slice of the relocations that apply to this symbol + let mut reloc_start = None; + loop { + // If we've reached the end of the relocations then we're done + if reloc_idx == relocations.len() { + break; + } + + // relocations behind the symbol start don't apply + if relocations[reloc_idx].0 < sym_offset { + break; + } + + // Set the head to the first relocation that applies + if reloc_start.is_none() { + reloc_start = Some(reloc_idx); + } + + reloc_idx += 1; + } + + // Identify the instructions that apply to this symbol + let data = match reloc_start { + Some(_start) => &data[sym_offset as usize..func_end], + _ => &[], + }; + + // Identify the relocations that apply to this symbol + let relocations = match reloc_start { + Some(start) => &relocations[start..reloc_idx], + None => &[], + }; + + syms.push(RelocatedSymbol { + name: sym.name().unwrap(), + sym, + offset: sym_offset as usize, + data, + relocations, + section: section_idx, + }); + + func_end = sym_offset as usize; + } + + assert_eq!(reloc_idx, relocations.len()); + + syms +} + +/// Compare two sets of bytes, masking out the bytes that are not part of the symbol +/// This is so we can compare functions with different relocations +fn compare_masked<'a>( + old: &impl Object<'a>, + new: &impl Object<'a>, + left: &RelocatedSymbol, + right: &RelocatedSymbol, +) -> bool { + // Make sure the relocations are the same length + if left.relocations.len() != right.relocations.len() { + return false; + } + + // Make sure the data is the same length + // If the size changed then the instructions are different (well, not necessarily, but enough) + if left.data.len() != right.data.len() { + return false; + } + + // Make sure the names match + if left.name != right.name { + return false; + } + + // We're going to walk from relocation target to target, but since there's no implicit target + // to start with, we simply use the end of the data + let mut last = left.data.len(); + + // Ensure the relocations point to the same symbol + // Data symbols are special ... todo + // + // relocations are in reverse order, so we can also compare the data as we go + for x in 0..left.relocations.len() { + // Grab the reloc + let (l_addr, left_reloc): &(u64, Relocation) = &left.relocations[x]; + let (_r_addr, right_reloc): &(u64, Relocation) = &right.relocations[x]; + + // The targets might not be same by index but should resolve to the same *name* + let left_target: RelocationTarget = left_reloc.target(); + let right_target: RelocationTarget = right_reloc.target(); + + // Use the name of the symbol to compare + // todo: decide if it's internal vs external + let left_name = symbol_name_of_relo(old, left_target); + let right_name = symbol_name_of_relo(new, right_target); + let (Some(left_name), Some(right_name)) = (left_name, right_name) else { + continue; + }; + + // Make sure the names match + // if the target is a locally defined symbol, then it might be the same + // todo(jon): hash the masked contents + if left_name != right_name { + return false; + } + + // Check the data + // the slice is the end of the relocation to the start of the previous relocation + let reloc_byte_size = (left_reloc.size() as usize) / 8; + let start = *l_addr as usize - left.offset as usize + reloc_byte_size; + + // Some relocations target the same location + // In these cases, we just continue since we just masked and checked them already + if (*l_addr as usize - left.offset as usize) == last { + continue; + } + + debug_assert!(start <= last); + debug_assert!(start <= left.data.len()); + + if &left.data[start..last] != &right.data[start..last] { + return false; + } + + if left_reloc.flags() != right_reloc.flags() { + return false; + } + + // todo: more checking... the symbols might be local + last = start - reloc_byte_size; + } + + // And a final check to make sure the data is the same + if left.data[..last] != right.data[..last] { + return false; + } + + true +} + +fn symbol_name_of_relo<'a>(obj: &impl Object<'a>, target: RelocationTarget) -> Option<&'a str> { + match target { + RelocationTarget::Symbol(symbol_index) => Some( + obj.symbol_by_index(symbol_index) + .unwrap() + .name_bytes() + .unwrap() + .to_utf8(), + ), + RelocationTarget::Section(_) => None, + RelocationTarget::Absolute => None, + _ => None, + } +} + +fn workspace_dir() -> PathBuf { + "/Users/jonkelley/Development/Tinkering/ipbp".into() +} + +trait ToUtf8<'a> { + fn to_utf8(&self) -> &'a str; +} + +impl<'a> ToUtf8<'a> for &'a [u8] { + fn to_utf8(&self) -> &'a str { + std::str::from_utf8(self).unwrap() + } +} + +fn make_stub_file( + proc_main_addr: u64, + patch_target: PathBuf, + adrp_imports: HashSet<&str>, +) -> Vec { + let data = fs::read(&patch_target).unwrap(); + let old = File::parse(&data as &[u8]).unwrap(); + let main_sym = old.symbol_by_name_bytes(b"_main").unwrap(); + let aslr_offset = proc_main_addr - main_sym.address(); + let addressed = old + .symbols() + .filter_map(|sym| { + adrp_imports + .get(sym.name().ok()?) + .copied() + .map(|o| (o, sym.address() + aslr_offset)) + }) + .collect::>(); + + build_stub( + old.format(), + old.architecture(), + old.endianness(), + addressed, + ) + .unwrap() +} + +/// Builds an object file that satisfies the imports +/// +/// Creates stub functions that jump to known addresses in a target process. +/// +/// .section __TEXT,__text +/// .globl __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E +/// .p2align 2 +/// __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E: +/// // Load 64-bit address using immediate values +/// movz x9, #0xCDEF // Bottom 16 bits +/// movk x9, #0x89AB, lsl #16 // Next 16 bits +/// movk x9, #0x4567, lsl #32 // Next 16 bits +/// movk x9, #0x0123, lsl #48 // Top 16 bits +/// +/// // Branch to the loaded address +/// br x9 +fn build_stub( + format: BinaryFormat, + architecture: Architecture, + endian: Endianness, + adrp_imports: HashMap<&str, u64>, +) -> Result> { + use object::{ + write::{Object, Symbol, SymbolSection}, + SectionKind, SymbolFlags, SymbolKind, SymbolScope, + }; + + // Create a new ARM64 object file + let mut obj = Object::new(format, architecture, endian); + + // Add a text section for our trampolines + let text_section = obj.add_section(Vec::new(), ".text".into(), SectionKind::Text); + + // For each symbol, create a trampoline that loads the immediate address and jumps to it + for (name, addr) in adrp_imports { + let mut trampoline = Vec::new(); + + // todo: writing these bytes are only good for arm64 + // + // + // Break down the 64-bit address into 16-bit chunks + let addr0 = (addr & 0xFFFF) as u16; // Bits 0-15 + let addr1 = ((addr >> 16) & 0xFFFF) as u16; // Bits 16-31 + let addr2 = ((addr >> 32) & 0xFFFF) as u16; // Bits 32-47 + let addr3 = ((addr >> 48) & 0xFFFF) as u16; // Bits 48-63 + + // MOVZ x9, #addr0 + let movz = 0xD2800009 | ((addr0 as u32) << 5); + trampoline.extend_from_slice(&movz.to_le_bytes()); + + // MOVK x9, #addr1, LSL #16 + let movk1 = 0xF2A00009 | ((addr1 as u32) << 5); + trampoline.extend_from_slice(&movk1.to_le_bytes()); + + // MOVK x9, #addr2, LSL #32 + let movk2 = 0xF2C00009 | ((addr2 as u32) << 5); + trampoline.extend_from_slice(&movk2.to_le_bytes()); + + // MOVK x9, #addr3, LSL #48 + let movk3 = 0xF2E00009 | ((addr3 as u32) << 5); + trampoline.extend_from_slice(&movk3.to_le_bytes()); + + // BR x9 - Branch to the address in x9 + let br: u32 = 0xD61F0120; + trampoline.extend_from_slice(&br.to_le_bytes()); + + // Add the trampoline to the text section + let symbol_offset = obj.append_section_data(text_section, &trampoline, 4); + + // we are writing this: + // __ZN93_$LT$generational_box..references..GenerationalRef$LT$R$GT$$u20$as$u20$core..fmt..Display$GT$3fmt17h455abb35572b9c11E + // + // but we should be writing this: + // _$LT$generational_box..references..GenerationalRef$LT$R$GT$$u20$as$u20$core..fmt..Display$GT$::fmt::h455abb35572b9c11 + // let name = strip_mangled(name); + + let name = if name.starts_with("_") { + &name[1..] + } else { + name + }; + + // Add the symbol + obj.add_symbol(Symbol { + name: name.into(), + value: symbol_offset, + size: trampoline.len() as u64, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + } + + obj.write().context("Failed to write object file") +} diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index c4e288ea1f..d6d8c2c690 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1,10 +1,11 @@ -use super::{progress::ProgressTx, BuildArtifacts}; +use super::{progress::ProgressTx, BuildArtifacts, PatchData}; use crate::dioxus_crate::DioxusCrate; use crate::{link::LinkAction, BuildArgs}; use crate::{AppBundle, Platform, Result, TraceSrc}; use anyhow::Context; use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV}; use dioxus_cli_opt::AssetManifest; +use krates::Utf8PathBuf; use serde::Deserialize; use std::{ path::{Path, PathBuf}, @@ -26,14 +27,23 @@ pub(crate) struct BuildRequest { /// The target directory for the build pub(crate) custom_target_dir: Option, + + /// The data for the binary patch + pub(crate) patch_data: Option, } impl BuildRequest { - pub fn new(krate: DioxusCrate, build: BuildArgs, progress: ProgressTx) -> Self { + pub fn new( + krate: DioxusCrate, + build: BuildArgs, + progress: ProgressTx, + patch_data: Option, + ) -> Self { Self { build, krate, progress, + patch_data, custom_target_dir: None, } } @@ -79,6 +89,12 @@ impl BuildRequest { let start = Instant::now(); self.prepare_build_dir()?; + + // // If we're able to do a binary patch + // if let Some(patch_data) = self.patch_data.as_ref() { + // return self.build_cargo_with_patch(patch_data).await; + // } + let exe = self.build_cargo().await?; let assets = self.collect_assets(&exe).await?; @@ -244,12 +260,6 @@ impl BuildRequest { return Ok(AssetManifest::default()); } - // Experimental feature for testing - if the env var is set, we'll use the deeplinker - if std::env::var("DEEPLINK").is_ok() { - tracing::debug!("Using deeplinker instead of incremental cache"); - return self.deep_linker_asset_extract().await; - } - // walk every file in the incremental cache dir, reading and inserting items into the manifest. let mut manifest = AssetManifest::default(); @@ -451,59 +461,6 @@ impl BuildRequest { }) } - /// We used to require traversing incremental artifacts for assets that were included but not - /// directly exposed to the final binary. Now, however, we force APIs to carry items created - /// from asset calls into top-level items such that they *do* get included in the final binary. - /// - /// There's a chance that's not actually true, so this function is kept around in case we do - /// need to revert to "deep extraction". - #[allow(unused)] - async fn deep_linker_asset_extract(&self) -> Result { - // Create a temp file to put the output of the args - // We need to do this since rustc won't actually print the link args to stdout, so we need to - // give `dx` a file to dump its env::args into - let tmp_file = tempfile::NamedTempFile::new()?; - - // Run `cargo rustc` again, but this time with a custom linker (dx) and an env var to force - // `dx` to act as a linker - // - // This will force `dx` to look through the incremental cache and find the assets from the previous build - Command::new("cargo") - .arg("rustc") - .args(self.build_arguments()) - .envs(self.env_vars()?) - .arg("--offline") /* don't use the network, should already be resolved */ - .arg("--") - .arg(format!( - "-Clinker={}", - std::env::current_exe() - .unwrap() - .canonicalize() - .unwrap() - .display() - )) - .env( - LinkAction::ENV_VAR_NAME, - LinkAction::BuildAssetManifest { - destination: tmp_file.path().to_path_buf().clone(), - } - .to_json(), - ) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await?; - - // The linker wrote the manifest to the temp file, let's load it! - let manifest = AssetManifest::load_from_file(tmp_file.path())?; - - if let Ok(path) = std::env::var("DEEPLINK").map(|s| s.parse::().unwrap()) { - _ = tmp_file.persist(path); - } - - Ok(manifest) - } - fn env_vars(&self) -> Result> { let mut env_vars = vec![]; @@ -968,4 +925,41 @@ impl BuildRequest { kotlin_dir } + + async fn build_cargo_with_patch(&self, patch_data: &PatchData) -> Result { + #[derive(Debug, Deserialize)] + struct RustcArtifact { + artifact: PathBuf, + emit: String, + } + + let mut child = Command::new(patch_data.direct_rustc[0].clone()) + .args(patch_data.direct_rustc[1..].iter()) + .env("HOTRELOAD_LINK", "reload") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let stdout = tokio::io::BufReader::new(child.stdout.take().unwrap()); + let stderr = tokio::io::BufReader::new(child.stderr.take().unwrap()); + let mut output_location = None; + let mut stdout = stdout.lines(); + let mut stderr = stderr.lines(); + + loop { + let line = tokio::select! { + Ok(Some(line)) = stdout.next_line() => line, + Ok(Some(line)) = stderr.next_line() => line, + else => break, + }; + + if let Ok(artifact) = serde_json::from_str::(&line) { + if artifact.emit == "link" { + output_location = Some(Utf8PathBuf::from_path_buf(artifact.artifact).unwrap()); + } + } + } + + todo!() + } } diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index a4711bd337..58e9304ed2 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -1,16 +1,13 @@ -use dioxus_cli_opt::AssetManifest; use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize)] pub enum LinkAction { - BuildAssetManifest { - destination: PathBuf, - }, LinkAndroid { linker: PathBuf, extra_flags: Vec, }, + FastLink {}, } impl LinkAction { @@ -53,53 +50,7 @@ impl LinkAction { .status() .expect("Failed to run android linker"); } - - // Assemble an asset manifest by walking the object files being passed to us - LinkAction::BuildAssetManifest { destination: dest } => { - let mut args: Vec<_> = std::env::args().collect(); - let mut manifest = AssetManifest::default(); - - // Handle command files, usually a windows thing. - if let Some(command) = args.iter().find(|arg| arg.starts_with('@')).cloned() { - let path = command.trim().trim_start_matches('@'); - let file_binary = std::fs::read(path).unwrap(); - - // This may be a utf-16le file. Let's try utf-8 first. - let content = String::from_utf8(file_binary.clone()).unwrap_or_else(|_| { - // Convert Vec to Vec to convert into a String - let binary_u16le: Vec = file_binary - .chunks_exact(2) - .map(|a| u16::from_le_bytes([a[0], a[1]])) - .collect(); - - String::from_utf16_lossy(&binary_u16le) - }); - - // Gather linker args, and reset the args to be just the linker args - args = content - .lines() - .map(|line| { - let line_parsed = line.to_string(); - let line_parsed = line_parsed.trim_end_matches('"').to_string(); - let line_parsed = line_parsed.trim_start_matches('"').to_string(); - line_parsed - }) - .collect(); - } - - // Parse through linker args for `.o` or `.rlib` files. - for item in args { - if item.ends_with(".o") || item.ends_with(".rlib") { - let path_to_item = PathBuf::from(item); - if let Ok(path) = path_to_item.canonicalize() { - _ = manifest.add_from_object_path(&path); - } - } - } - - let contents = serde_json::to_string(&manifest).expect("Failed to write manifest"); - std::fs::write(dest, contents).expect("Failed to write output file"); - } + LinkAction::FastLink {} => {} } } } diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 1ab9e22c05..a4f8235cce 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -38,6 +38,10 @@ pub(crate) struct ServeArgs { #[arg(long, default_missing_value="true", num_args=0..=1, short = 'i')] pub(crate) interactive: Option, + /// Build this binary using binary patching instead of a full rebuild [default: false] + #[arg(long, default_value_t = false)] + pub(crate) binary_patch: bool, + /// Arguments for the build itself #[clap(flatten)] pub(crate) build_arguments: BuildArgs, diff --git a/packages/cli/src/config.rs b/packages/cli/src/config/mod.rs similarity index 100% rename from packages/cli/src/config.rs rename to packages/cli/src/config/mod.rs diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index ea94da8751..f3a705e741 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -89,39 +89,44 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> { let file = files[0].display().to_string(); let file = file.trim_start_matches(&krate.crate_dir().display().to_string()); - // if change is hotreloadable, hotreload it - // and then send that update to all connected clients - if let Some(hr) = runner.attempt_hot_reload(files).await { - // Only send a hotreload message for templates and assets - otherwise we'll just get a full rebuild - // - // Also make sure the builder isn't busy since that might cause issues with hotreloads - // https://github.com/DioxusLabs/dioxus/issues/3361 - if hr.is_empty() || !builder.can_receive_hotreloads() { - tracing::debug!(dx_src = ?TraceSrc::Dev, "Ignoring file change: {}", file); - continue; + match runner.hotreload(files).await { + crate::ReloadKind::Rsx => { + // // Only send a hotreload message for templates and assets - otherwise we'll just get a full rebuild + // // + // // Also make sure the builder isn't busy since that might cause issues with hotreloads + // // https://github.com/DioxusLabs/dioxus/issues/3361 + // if hr.is_empty() || !builder.can_receive_hotreloads() { + // tracing::debug!(dx_src = ?TraceSrc::Dev, "Ignoring file change: {}", file); + // continue; + // } + + // tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {}", file); + + // devserver.send_hotreload(hr).await; } - tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {}", file); + crate::ReloadKind::Binary => todo!(), - devserver.send_hotreload(hr).await; - } else if runner.should_full_rebuild { - tracing::info!(dx_src = ?TraceSrc::Dev, "Full rebuild: {}", file); + crate::ReloadKind::Full if runner.should_full_rebuild => { + tracing::info!(dx_src = ?TraceSrc::Dev, "Full rebuild: {}", file); - // We're going to kick off a new build, interrupting the current build if it's ongoing - builder.rebuild(args.build_arguments.clone()); + // We're going to kick off a new build, interrupting the current build if it's ongoing + builder.rebuild(args.build_arguments.clone()); - // Clear the hot reload changes so we don't have out-of-sync issues with changed UI - runner.clear_hot_reload_changes(); - runner.file_map.force_rebuild(); + // Clear the hot reload changes so we don't have out-of-sync issues with changed UI + runner.clear_hot_reload_changes(); + runner.file_map.force_rebuild(); - // Tell the server to show a loading page for any new requests - devserver.send_reload_start().await; - devserver.start_build().await; - } else { - tracing::warn!( - "Rebuild required but is currently paused - press `r` to rebuild manually" - ) - } + // Tell the server to show a loading page for any new requests + devserver.send_reload_start().await; + devserver.start_build().await; + } + crate::ReloadKind::Full => { + tracing::warn!( + "Rebuild required but is currently paused - press `r` to rebuild manually" + ) + } + }; } // Run the server in the background diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 597c47753f..41fde080b0 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -1,6 +1,7 @@ use super::{AppHandle, ServeUpdate, WebServer}; use crate::{ - AppBundle, DioxusCrate, HotreloadFilemap, HotreloadResult, Platform, Result, TraceSrc, + AppBundle, DioxusCrate, HotreloadFilemap, HotreloadResult, Platform, ReloadKind, Result, + TraceSrc, }; use dioxus_core::internal::TemplateGlobalKey; use dioxus_devtools_types::HotReloadMsg; @@ -151,10 +152,7 @@ impl AppRunner { } } - pub(crate) async fn attempt_hot_reload( - &mut self, - modified_files: Vec, - ) -> Option { + pub(crate) async fn hotreload(&mut self, modified_files: Vec) -> ReloadKind { // If we have any changes to the rust files, we need to update the file map let mut templates = vec![]; @@ -177,7 +175,7 @@ impl AppRunner { // Special-case the Cargo.toml file - we want updates here to cause a full rebuild if path.file_name().and_then(|v| v.to_str()) == Some("Cargo.toml") { - return None; + return ReloadKind::Full; } // Otherwise, it might be an asset and we should look for it in all the running apps diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index e3ba8087ae..7c878fea46 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -18,7 +18,7 @@ pub(crate) trait AnyProps: 'static { } /// A component along with the props the component uses to render. -pub(crate) struct VProps, P, M> { +pub(crate) struct VProps, P: 'static, M> { render_fn: F, memo: fn(&mut P, &P) -> bool, props: P, diff --git a/packages/core/src/diff/component.rs b/packages/core/src/diff/component.rs index f66872ebcc..d1a2b71fc4 100644 --- a/packages/core/src/diff/component.rs +++ b/packages/core/src/diff/component.rs @@ -134,7 +134,9 @@ impl VNode { dom: &mut VirtualDom, to: Option<&mut impl WriteMutations>, ) { - // Replace components that have different render fns + // Replace components that have different render fns - only in release mode + // During development we might want to hot-reload components + #[cfg(not(debug_assertions))] if old.render_fn != new.render_fn { return self.replace_vcomponent(mount, idx, new, parent, dom, to); } diff --git a/packages/core/src/diff/node.rs b/packages/core/src/diff/node.rs index 7225969e42..a639b5f9ce 100644 --- a/packages/core/src/diff/node.rs +++ b/packages/core/src/diff/node.rs @@ -29,11 +29,13 @@ impl VNode { ); // If the templates are different, we need to replace the entire template + // if cfg!(not(debug_assertions)) { if self.template != new.template { let mount_id = self.mount.get(); let parent = dom.get_mounted_parent(mount_id); return self.replace(std::slice::from_ref(new), parent, dom, to); } + // } self.move_mount_to(new, dom); diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 08d048c6b1..2d49698cc6 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -116,7 +116,7 @@ where /// A warning that will trigger if a component is called as a function #[warnings::warning] -pub(crate) fn component_called_as_function, P, M>(_: C) { +pub(crate) fn component_called_as_function, P: 'static, M>(_: C) { // We trim WithOwner from the end of the type name for component with a builder that include a special owner which may not match the function name directly let type_name = std::any::type_name::(); let component_name = Runtime::with(|rt| { @@ -139,7 +139,9 @@ pub(crate) fn component_called_as_function, P, M>(_: /// Make sure that this component is currently running as a component, not a function call #[doc(hidden)] #[allow(clippy::no_effect)] -pub fn verify_component_called_as_component, P, M>(component: C) { +pub fn verify_component_called_as_component, P: 'static, M>( + component: C, +) { component_called_as_function(component); } @@ -166,10 +168,13 @@ pub fn verify_component_called_as_component, P, M>(co note = "You may have forgotten to add `#[component]` to your function to automatically implement the `ComponentFunction` trait." ) )] -pub trait ComponentFunction: Clone + 'static { +pub trait ComponentFunction: Clone + 'static +where + Props: 'static, +{ /// Get the type id of the component. fn id(&self) -> TypeId { - TypeId::of::() + TypeId::of::() } /// Convert the component to a function that takes props and returns an element. @@ -177,7 +182,10 @@ pub trait ComponentFunction: Clone + 'static { } /// Accept any callbacks that take props -impl Element + Clone + 'static, P> ComponentFunction

for F { +impl Element + Clone + 'static, P> ComponentFunction

for F +where + P: 'static, +{ fn rebuild(&self, props: P) -> Element { self(props) } From 309e5e65c934cf486af13997bd7de251443a63a0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 24 Feb 2025 22:47:35 -0800 Subject: [PATCH 002/301] inline more logic --- Cargo.lock | 1 + packages/cli/Cargo.toml | 1 + packages/cli/src/build/bundle.rs | 7 +- packages/cli/src/build/patch.rs | 117 ++++++++++++++++++++++++++ packages/cli/src/build/request.rs | 27 ++++-- packages/cli/src/cli/link.rs | 91 ++++++++++++++++++++- packages/cli/src/filemap.rs | 9 +- packages/cli/src/serve/mod.rs | 42 +--------- packages/cli/src/serve/runner.rs | 127 +++++++++++++++++------------ packages/devtools-types/src/lib.rs | 3 +- 10 files changed, 312 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff935a7a92..f09f96b137 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3550,6 +3550,7 @@ dependencies = [ "rustls 0.23.19", "serde", "serde_json", + "shell-words", "strum 0.26.3", "syn 2.0.90", "tar", diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 43afefe310..f22df97865 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -101,6 +101,7 @@ ansi-to-tui = "6.0" ansi-to-html = "0.2.1" path-absolutize = "3.1" ratatui = { version = "0.28.0", features = ["crossterm", "unstable"] } +shell-words = "1.1.0" # disable `log` entirely since `walrus` uses it and is *much* slower with it enableda log = { version = "0.4", features = ["max_level_off", "release_max_level_off"] } diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index e2b769f9d2..4496fcc274 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -96,6 +96,7 @@ pub(crate) struct AppBundle { #[derive(Debug)] pub struct BuildArtifacts { pub(crate) exe: PathBuf, + pub(crate) direct_rustc: Vec>, pub(crate) assets: AssetManifest, pub(crate) time_taken: Duration, } @@ -269,10 +270,6 @@ impl AppBundle { tracing::debug!("Assembling app bundle"); bundle.build.status_start_bundle(); - /* - assume the build dir is already created by BuildRequest - todo(jon): maybe refactor this a bit to force AppBundle to be created before it can be filled in - */ bundle .write_main_executable() .await @@ -952,4 +949,6 @@ impl AppBundle { std::fs::copy(source, destination)?; Ok(()) } + + async fn binary_patch(&self) {} } diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 3729effe15..025d279882 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -28,6 +28,123 @@ pub struct PatchData { pub direct_rustc: Vec, } +pub async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, out_path: PathBuf) { + let mut object = ObjectDiff::new().unwrap(); + object.load().unwrap(); + + let all_exports = object + .new + .iter() + .flat_map(|(_, f)| f.file.exports().unwrap()) + .map(|e| e.name().to_utf8()) + .collect::>(); + + let mut adrp_imports = HashSet::new(); + + let mut satisfied_exports = HashSet::new(); + + let modified_symbols = object + .modified_symbols + .iter() + .map(|f| f.as_str()) + .collect::>(); + + if modified_symbols.is_empty() { + println!("No modified symbols"); + } + + let mut modified_log = String::new(); + for m in modified_symbols.iter() { + // if m.starts_with("l") { + // continue; + // } + + let path = object.find_path_to_main(m); + println!("m: {m}"); + println!("path: {path:#?}\n"); + modified_log.push_str(&format!("{m}\n")); + modified_log.push_str(&format!("{path:#?}\n")); + } + std::fs::write(workspace_dir().join("modified_symbols.txt"), modified_log).unwrap(); + + let modified = object + .modified_files + .iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .collect::>(); + + // Figure out which symbols are required from *existing* code + // We're going to create a stub `.o` file that satisfies these by jumping into the original code via a dynamic lookup / and or literally just manually doing it + for fil in modified.iter() { + let f = object + .new + .get(fil.0.file_name().unwrap().to_str().unwrap()) + .unwrap(); + + for i in f.file.imports().unwrap() { + if all_exports.contains(i.name().to_utf8()) { + adrp_imports.insert(i.name().to_utf8()); + } + } + + for e in f.file.exports().unwrap() { + satisfied_exports.insert(e.name().to_utf8()); + } + } + + // Remove any imports that are indeed satisifed + for s in satisfied_exports.iter() { + adrp_imports.remove(s); + } + + // Assemble the stub + let stub_data = make_stub_file(proc_main_addr, patch_target, adrp_imports); + let stub_file = workspace_dir().join("stub.o"); + std::fs::write(&stub_file, stub_data).unwrap(); + + let out = Command::new("cc") + .args(modified.iter().map(|(f, _)| f)) + .arg(stub_file) + .arg("-dylib") + .arg("-Wl,-undefined,dynamic_lookup") + .arg("-Wl,-unexported_symbol,_main") + .arg("-arch") + .arg("arm64") + .arg("-dead_strip") + .arg("-o") + .arg(out_path) + .output() + .await + .unwrap(); + + let err = String::from_utf8_lossy(&out.stderr); + println!("err: {err}"); + std::fs::write(workspace_dir().join("link_errs_partial.txt"), &*err).unwrap(); + + // // -O0 ? supposedly faster + // // -reproducible - even better? + // // -exported_symbol and friends - could help with dead-code stripping + // // -e symbol_name - for setting the entrypoint + // // -keep_relocs ? + + // // run the linker, but unexport the `_main` symbol + // let res = Command::new("cc") + // .args(object_files) + // .arg("-dylib") + // .arg("-undefined") + // .arg("dynamic_lookup") + // .arg("-Wl,-unexported_symbol,_main") + // .arg("-arch") + // .arg("arm64") + // .arg("-dead_strip") // maybe? + // .arg("-o") + // .arg(&out_file) + // .stdout(Stdio::piped()) + // .stderr(Stdio::piped()) + // .output() + // .await?; +} + struct ObjectDiff { old: BTreeMap, new: BTreeMap, diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index d6d8c2c690..bda7561ec1 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -90,16 +90,17 @@ impl BuildRequest { let start = Instant::now(); self.prepare_build_dir()?; - // // If we're able to do a binary patch - // if let Some(patch_data) = self.patch_data.as_ref() { - // return self.build_cargo_with_patch(patch_data).await; - // } + // If we're able to do a binary patch + if let Some(patch_data) = self.patch_data.as_ref() { + return self.build_cargo_with_patch(patch_data).await; + } - let exe = self.build_cargo().await?; + let (exe, direct_rustc) = self.build_cargo().await?; let assets = self.collect_assets(&exe).await?; Ok(BuildArtifacts { exe, + direct_rustc, assets, time_taken: start.elapsed(), }) @@ -120,7 +121,7 @@ impl BuildRequest { /// Run `cargo`, returning the location of the final executable /// /// todo: add some stats here, like timing reports, crate-graph optimizations, etc - pub(crate) async fn build_cargo(&self) -> Result { + pub(crate) async fn build_cargo(&self) -> Result<(PathBuf, Vec>)> { tracing::debug!("Executing cargo..."); // Extract the unit count of the crate graph so build_cargo has more accurate data @@ -180,6 +181,7 @@ impl BuildRequest { let mut stderr = stderr.lines(); let mut units_compiled = 0; let mut emitting_error = false; + let mut direct_rustc = Vec::new(); loop { use cargo_metadata::Message; @@ -197,6 +199,19 @@ impl BuildRequest { match message { Message::BuildScriptExecuted(_) => units_compiled += 1, Message::TextLine(line) => { + if line.trim().starts_with("Running ") { + // trim everyting but the contents between the quotes + let args = line + .trim() + .trim_start_matches("Running `") + .trim_end_matches('`'); + + // Parse these as shell words so we can get the direct rustc args + if let Ok(split) = shell_words::split(args) { + direct_rustc.push(split); + } + } + // For whatever reason, if there's an error while building, we still receive the TextLine // instead of an "error" message. However, the following messages *also* tend to // be the error message, and don't start with "error:". So we'll check if we've already diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index 58e9304ed2..10ec0224c0 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -1,5 +1,7 @@ +use crate::{Platform, Result}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use tokio::process::Command; #[derive(Debug, Serialize, Deserialize)] pub enum LinkAction { @@ -7,7 +9,12 @@ pub enum LinkAction { linker: PathBuf, extra_flags: Vec, }, - FastLink {}, + FatLink { + platform: Platform, + }, + ThinLink { + platform: Platform, + }, } impl LinkAction { @@ -35,9 +42,11 @@ impl LinkAction { /// /// hmmmmmmmm tbh I'd rather just pass the object files back and do the parsing here, but the interface /// is nicer to just bounce back the args and let the host do the parsing/canonicalization - pub(crate) fn run(self) { + pub(crate) async fn run(self) -> Result<()> { + let args = std::env::args().collect::>(); + match self { - // Literally just run the android linker :) + // Run the android linker passed to us via the env var LinkAction::LinkAndroid { linker, extra_flags, @@ -50,7 +59,81 @@ impl LinkAction { .status() .expect("Failed to run android linker"); } - LinkAction::FastLink {} => {} + + // Run the system linker but keep any unused sections + LinkAction::FatLink { platform } => { + let args = args + .into_iter() + .skip(1) + .filter(|arg| arg != "-Wl,-dead_strip") + .collect::>(); + + let object_files: Vec<_> = args.iter().filter(|arg| arg.ends_with(".o")).collect(); + cache_incrementals(object_files.as_ref()); + + // Run ld with the args + let res = Command::new("cc").args(args).output().await?; + let err = String::from_utf8_lossy(&res.stderr); + } + + // Run the linker but without rlibs + LinkAction::ThinLink { platform } => { + let index_of_out = args.iter().position(|arg| arg == "-o").unwrap(); + let out_file = args[index_of_out + 1].clone(); + let object_files: Vec<_> = args.iter().filter(|arg| arg.ends_with(".o")).collect(); + + cache_incrementals(object_files.as_ref()); + + let patch_target = + "/Users/jonkelley/Development/Tinkering/ipbp/target/hotreload/harness".into(); + + let main_ptr = std::fs::read_to_string(workspace_root().join("harnessaddr.txt")) + .unwrap() + .parse() + .unwrap(); + + crate::build::attempt_partial_link(main_ptr, patch_target, out_file.clone().into()) + .await; + } } + + Ok(()) + } +} + +/// Move all previous object files to "incremental-old" and all new object files to "incremental-new" +fn cache_incrementals(object_files: &[&String]) { + let old = workspace_root().join("data").join("incremental-old"); + let new = workspace_root().join("data").join("incremental-new"); + + // Remove the old incremental-old directory if it exists + _ = std::fs::remove_dir_all(&old); + + // Rename incremental-new to incremental-old if it exists. Faster than moving all the files + _ = std::fs::rename(&new, &old); + + // Create the new incremental-new directory to place the outputs in + std::fs::create_dir_all(&new).unwrap(); + + // Now drop in all the new object files + for o in object_files.iter() { + if !o.ends_with(".rcgu.o") { + continue; + } + + let path = PathBuf::from(o); + std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); } } + +fn system_linker(platform: Platform) -> &'static str { + // match platform { + // Platform::MacOS => "ld", + // Platform::Windows => "ld", + // Platform::Linux => "ld", + // Platform::Ios => "ld", + // Platform::Android => "ld", + // Platform::Server => "ld", + // Platform::Liveview => "ld", + // } +} diff --git a/packages/cli/src/filemap.rs b/packages/cli/src/filemap.rs index 33c3f2e428..0dcca44741 100644 --- a/packages/cli/src/filemap.rs +++ b/packages/cli/src/filemap.rs @@ -79,12 +79,12 @@ impl HotreloadFilemap { /// This does not do any caching on what intermediate state, like previous hotreloads, so you need /// to do that yourself. pub(crate) fn update_rsx( - &mut self, + &self, path: &Path, new_contents: String, ) -> HotreloadResult { // Get the cached file if it exists - let Some(cached_file) = self.map.get_mut(path) else { + let Some(cached_file) = self.map.get(path) else { return HotreloadResult::NotParseable; }; @@ -99,13 +99,12 @@ impl HotreloadFilemap { return HotreloadResult::NotParseable; }; - // 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); + // // 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); // todo(jon): allow server-fn hotreloading // also whyyyyyyyyy is this (new, old) instead of (old, new)? smh smh smh let Some(changed_rsx) = dioxus_rsx_hotreload::diff_rsx(&new_file, &old_file) else { - tracing::debug!("Diff rsx returned notreladable"); return HotreloadResult::Notreloadable; }; diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index f3a705e741..c389a5c3d4 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -86,47 +86,7 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> { continue; } - let file = files[0].display().to_string(); - let file = file.trim_start_matches(&krate.crate_dir().display().to_string()); - - match runner.hotreload(files).await { - crate::ReloadKind::Rsx => { - // // Only send a hotreload message for templates and assets - otherwise we'll just get a full rebuild - // // - // // Also make sure the builder isn't busy since that might cause issues with hotreloads - // // https://github.com/DioxusLabs/dioxus/issues/3361 - // if hr.is_empty() || !builder.can_receive_hotreloads() { - // tracing::debug!(dx_src = ?TraceSrc::Dev, "Ignoring file change: {}", file); - // continue; - // } - - // tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {}", file); - - // devserver.send_hotreload(hr).await; - } - - crate::ReloadKind::Binary => todo!(), - - crate::ReloadKind::Full if runner.should_full_rebuild => { - tracing::info!(dx_src = ?TraceSrc::Dev, "Full rebuild: {}", file); - - // We're going to kick off a new build, interrupting the current build if it's ongoing - builder.rebuild(args.build_arguments.clone()); - - // Clear the hot reload changes so we don't have out-of-sync issues with changed UI - runner.clear_hot_reload_changes(); - runner.file_map.force_rebuild(); - - // Tell the server to show a loading page for any new requests - devserver.send_reload_start().await; - devserver.start_build().await; - } - crate::ReloadKind::Full => { - tracing::warn!( - "Rebuild required but is currently paused - press `r` to rebuild manually" - ) - } - }; + runner.hotreload(files, &mut builder, &mut devserver).await; } // Run the server in the background diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 41fde080b0..d9fa476c36 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -152,7 +152,16 @@ impl AppRunner { } } - pub(crate) async fn hotreload(&mut self, modified_files: Vec) -> ReloadKind { + /// Attempt to hotreload the given files + pub(crate) async fn hotreload( + &mut self, + modified_files: Vec, + builder: &mut crate::Builder, + server: &mut crate::serve::WebServer, + ) { + let file = modified_files[0].display().to_string(); + let file = file.trim_start_matches(&self.krate.crate_dir().display().to_string()); + // If we have any changes to the rust files, we need to update the file map let mut templates = vec![]; @@ -169,15 +178,38 @@ impl AppRunner { // If it's a rust file, we want to hotreload it using the filemap if ext == "rs" { - edited_rust_files.push(path); + // Strip the prefix before sending it to the filemap + let Ok(path) = path.strip_prefix(self.krate.workspace_dir()) else { + tracing::error!( + "Hotreloading file outside of the crate directory: {:?}", + path + ); + continue; + }; + + // And grabout the contents + let Ok(contents) = std::fs::read_to_string(&path) else { + tracing::debug!("Failed to read rust file while hotreloading: {:?}", path); + continue; + }; + + match self.file_map.update_rsx::(path, contents) { + HotreloadResult::Rsx(new) => templates.extend(new), + + // The rust file may have failed to parse, but that is most likely + // because the user is in the middle of adding new code + // We just ignore the error and let Rust analyzer warn about the problem + HotreloadResult::Notreloadable => { + // return ReloadKind::Binary + } + + HotreloadResult::NotParseable => { + tracing::debug!(dx_src = ?TraceSrc::Dev, "Error hotreloading file - not parseable {path:?}") + } + } continue; } - // Special-case the Cargo.toml file - we want updates here to cause a full rebuild - if path.file_name().and_then(|v| v.to_str()) == Some("Cargo.toml") { - return ReloadKind::Full; - } - // Otherwise, it might be an asset and we should look for it in all the running apps if let Some(runner) = self.running.as_mut() { if let Some(bundled_name) = runner.hotreload_bundled_asset(&path).await { @@ -188,53 +220,51 @@ impl AppRunner { } } - // Multiple runners might have queued the same asset, so dedup them - assets.dedup(); - - // Process the rust files - for rust_file in edited_rust_files { - // Strip the prefix before sending it to the filemap - let Ok(path) = rust_file.strip_prefix(self.krate.workspace_dir()) else { - tracing::error!( - "Hotreloading file outside of the crate directory: {:?}", - rust_file - ); - continue; - }; + // let msg = HotReloadMsg { templates, assets }; - // And grabout the contents - let Ok(contents) = std::fs::read_to_string(&rust_file) else { - tracing::debug!( - "Failed to read rust file while hotreloading: {:?}", - rust_file - ); - continue; - }; + // self.add_hot_reload_message(&msg); - match self.file_map.update_rsx::(path, contents) { - HotreloadResult::Rsx(new) => templates.extend(new), + // // Only send a hotreload message for templates and assets - otherwise we'll just get a full rebuild + // // + // // Also make sure the builder isn't busy since that might cause issues with hotreloads + // // https://github.com/DioxusLabs/dioxus/issues/3361 + // if hr.is_empty() || !builder.can_receive_hotreloads() { + // tracing::debug!(dx_src = ?TraceSrc::Dev, "Ignoring file change: {}", file); + // continue; + // } - // The rust file may have failed to parse, but that is most likely - // because the user is in the middle of adding new code - // We just ignore the error and let Rust analyzer warn about the problem - HotreloadResult::Notreloadable => return None, - HotreloadResult::NotParseable => { - tracing::debug!(dx_src = ?TraceSrc::Dev, "Error hotreloading file - not parseable {rust_file:?}") - } - } - } + // tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {}", file); - let msg = HotReloadMsg { - templates, - assets, - unknown_files: vec![], - }; + // devserver.send_hotreload(hr).await; + // } + + // match { + // crate::ReloadKind::Full if runner.should_full_rebuild => { + // tracing::info!(dx_src = ?TraceSrc::Dev, "Full rebuild: {}", file); + + // // We're going to kick off a new build, interrupting the current build if it's ongoing + // builder.rebuild(args.build_arguments.clone()); - self.add_hot_reload_message(&msg); + // // Clear the hot reload changes so we don't have out-of-sync issues with changed UI + // runner.clear_hot_reload_changes(); + // runner.file_map.force_rebuild(); - Some(msg) + // // Tell the server to show a loading page for any new requests + // devserver.send_reload_start().await; + // devserver.start_build().await; + // } + // crate::ReloadKind::Full => { + // tracing::warn!( + // "Rebuild required but is currently paused - press `r` to rebuild manually" + // ) + // } + // }; + + todo!() } + fn attempt_rsx_hotreload(&mut self) {} + /// Get any hot reload changes that have been applied since the last full rebuild pub(crate) fn applied_hot_reload_changes(&mut self) -> HotReloadMsg { self.applied_hot_reload_message.clone() @@ -257,17 +287,12 @@ impl AppRunner { .collect(); let mut assets: HashSet = std::mem::take(&mut applied.assets).into_iter().collect(); - let mut unknown_files: HashSet = std::mem::take(&mut applied.unknown_files) - .into_iter() - .collect(); for template in &msg.templates { templates.insert(template.key.clone(), template.clone()); } assets.extend(msg.assets.iter().cloned()); - unknown_files.extend(msg.unknown_files.iter().cloned()); applied.templates = templates.into_values().collect(); applied.assets = assets.into_iter().collect(); - applied.unknown_files = unknown_files.into_iter().collect(); } pub(crate) async fn client_connected(&mut self) { diff --git a/packages/devtools-types/src/lib.rs b/packages/devtools-types/src/lib.rs index 0450e02361..cbd08ce721 100644 --- a/packages/devtools-types/src/lib.rs +++ b/packages/devtools-types/src/lib.rs @@ -37,11 +37,10 @@ pub enum ClientMsg { pub struct HotReloadMsg { pub templates: Vec, pub assets: Vec, - pub unknown_files: Vec, } impl HotReloadMsg { pub fn is_empty(&self) -> bool { - self.templates.is_empty() && self.assets.is_empty() && self.unknown_files.is_empty() + self.templates.is_empty() && self.assets.is_empty() } } From 5e2c2471800aebb6da7741cfe119c52b69b0d937 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 24 Feb 2025 23:01:30 -0800 Subject: [PATCH 003/301] fix new linker --- packages/cli/src/build/patch.rs | 36 +++++++++++----- packages/cli/src/cli/link.rs | 75 ++++++++++++++++++--------------- packages/cli/src/main.rs | 2 +- 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 025d279882..2463266250 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -12,6 +12,8 @@ use std::{ }; use tokio::process::Command; +use crate::Platform; + pub enum ReloadKind { /// An RSX-only patch Rsx, @@ -28,8 +30,14 @@ pub struct PatchData { pub direct_rustc: Vec, } -pub async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, out_path: PathBuf) { - let mut object = ObjectDiff::new().unwrap(); +pub async fn attempt_partial_link( + old_cache: PathBuf, + new_cache: PathBuf, + proc_main_addr: u64, + patch_target: PathBuf, + out_path: PathBuf, +) { + let mut object = ObjectDiff::new(old_cache, new_cache).unwrap(); object.load().unwrap(); let all_exports = object @@ -65,7 +73,6 @@ pub async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, ou modified_log.push_str(&format!("{m}\n")); modified_log.push_str(&format!("{path:#?}\n")); } - std::fs::write(workspace_dir().join("modified_symbols.txt"), modified_log).unwrap(); let modified = object .modified_files @@ -145,6 +152,19 @@ pub async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, ou // .await?; } +fn system_cc(platform: Platform) -> &'static str { + match platform { + Platform::MacOS => "cc", + Platform::Windows => "cc", + Platform::Linux => "cc", + Platform::Ios => "cc", + Platform::Android => "cc", + Platform::Server => "cc", + Platform::Liveview => "cc", + Platform::Web => "wasm-ld", + } +} + struct ObjectDiff { old: BTreeMap, new: BTreeMap, @@ -154,10 +174,10 @@ struct ObjectDiff { } impl ObjectDiff { - fn new() -> Result { + fn new(old_cache: PathBuf, new_cache: PathBuf) -> Result { Ok(Self { - old: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-old"))?, - new: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-new"))?, + old: LoadedFile::from_dir(&old_cache)?, + new: LoadedFile::from_dir(&new_cache)?, modified_files: Default::default(), modified_symbols: Default::default(), parents: Default::default(), @@ -611,10 +631,6 @@ fn symbol_name_of_relo<'a>(obj: &impl Object<'a>, target: RelocationTarget) -> O } } -fn workspace_dir() -> PathBuf { - "/Users/jonkelley/Development/Tinkering/ipbp".into() -} - trait ToUtf8<'a> { fn to_utf8(&self) -> &'a str; } diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index 10ec0224c0..9df9be8a93 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -11,9 +11,13 @@ pub enum LinkAction { }, FatLink { platform: Platform, + incremental_dir: PathBuf, }, ThinLink { platform: Platform, + main_ptr: u64, + patch_target: PathBuf, + incremental_dir: PathBuf, }, } @@ -60,16 +64,29 @@ impl LinkAction { .expect("Failed to run android linker"); } - // Run the system linker but keep any unused sections - LinkAction::FatLink { platform } => { + // Run the system linker but keep any unused sections. + // + // This ensures our thin link will work against the binaries built here. + LinkAction::FatLink { + platform, + incremental_dir, + } => { + // Make sure we *don't* dead-strip the binary so every symbol still exists let args = args .into_iter() .skip(1) .filter(|arg| arg != "-Wl,-dead_strip") .collect::>(); - let object_files: Vec<_> = args.iter().filter(|arg| arg.ends_with(".o")).collect(); - cache_incrementals(object_files.as_ref()); + // Persist the cache of incremental files + cache_incrementals( + &incremental_dir.join("old"), + &incremental_dir.join("new"), + args.iter() + .filter(|arg| arg.ends_with(".o")) + .collect::>() + .as_ref(), + ); // Run ld with the args let res = Command::new("cc").args(args).output().await?; @@ -77,23 +94,30 @@ impl LinkAction { } // Run the linker but without rlibs - LinkAction::ThinLink { platform } => { + LinkAction::ThinLink { + platform, + patch_target, + incremental_dir, + main_ptr, + } => { let index_of_out = args.iter().position(|arg| arg == "-o").unwrap(); let out_file = args[index_of_out + 1].clone(); let object_files: Vec<_> = args.iter().filter(|arg| arg.ends_with(".o")).collect(); - cache_incrementals(object_files.as_ref()); - - let patch_target = - "/Users/jonkelley/Development/Tinkering/ipbp/target/hotreload/harness".into(); - - let main_ptr = std::fs::read_to_string(workspace_root().join("harnessaddr.txt")) - .unwrap() - .parse() - .unwrap(); - - crate::build::attempt_partial_link(main_ptr, patch_target, out_file.clone().into()) - .await; + cache_incrementals( + &incremental_dir.join("old"), + &incremental_dir.join("new"), + object_files.as_ref(), + ); + + crate::build::attempt_partial_link( + incremental_dir.join("old"), + incremental_dir.join("new"), + main_ptr, + patch_target, + out_file.clone().into(), + ) + .await; } } @@ -102,10 +126,7 @@ impl LinkAction { } /// Move all previous object files to "incremental-old" and all new object files to "incremental-new" -fn cache_incrementals(object_files: &[&String]) { - let old = workspace_root().join("data").join("incremental-old"); - let new = workspace_root().join("data").join("incremental-new"); - +fn cache_incrementals(old: &PathBuf, new: &PathBuf, object_files: &[&String]) { // Remove the old incremental-old directory if it exists _ = std::fs::remove_dir_all(&old); @@ -125,15 +146,3 @@ fn cache_incrementals(object_files: &[&String]) { std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); } } - -fn system_linker(platform: Platform) -> &'static str { - // match platform { - // Platform::MacOS => "ld", - // Platform::Windows => "ld", - // Platform::Linux => "ld", - // Platform::Ios => "ld", - // Platform::Android => "ld", - // Platform::Server => "ld", - // Platform::Liveview => "ld", - // } -} diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 436615ee37..a73a7d6425 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -37,7 +37,7 @@ pub(crate) use settings::*; async fn main() { // If we're being ran as a linker (likely from ourselves), we want to act as a linker instead. if let Some(link_action) = link::LinkAction::from_env() { - return link_action.run(); + return link_action.run().await.unwrap(); } let args = TraceController::initialize(); From 609f5950c335953d988183d1ef1e7d465f575759 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 25 Feb 2025 18:21:38 -0800 Subject: [PATCH 004/301] wip --- packages/cli/src/build/builder.rs | 21 ++- packages/cli/src/build/bundle.rs | 47 ++++-- packages/cli/src/build/patch.rs | 50 +++---- packages/cli/src/build/request.rs | 195 ++++++++++++------------ packages/cli/src/build/web.rs | 5 +- packages/cli/src/cli/link.rs | 23 ++- packages/cli/src/filemap.rs | 167 --------------------- packages/cli/src/main.rs | 2 - packages/cli/src/serve/handle.rs | 2 +- packages/cli/src/serve/mod.rs | 60 ++++++-- packages/cli/src/serve/runner.rs | 232 ++++++++++++++++++++--------- packages/cli/src/serve/server.rs | 8 + packages/desktop/src/app.rs | 2 +- packages/desktop/src/ipc.rs | 2 +- packages/desktop/src/launch.rs | 2 +- packages/devtools-types/src/lib.rs | 4 +- packages/web/src/devtools.rs | 16 +- 17 files changed, 428 insertions(+), 410 deletions(-) delete mode 100644 packages/cli/src/filemap.rs diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index f25ed25201..5c587dad13 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -4,6 +4,8 @@ use crate::{ }; use std::time::{Duration, Instant}; +use super::BuildMode; + /// The component of the serve engine that watches ongoing builds and manages their state, handle, /// and progress. /// @@ -17,6 +19,7 @@ pub(crate) struct Builder { pub krate: DioxusCrate, pub request: BuildRequest, pub build: tokio::task::JoinHandle>, + pub bundle: tokio::task::JoinHandle>, pub tx: ProgressTx, pub rx: ProgressRx, @@ -39,7 +42,7 @@ impl Builder { /// Create a new builder and immediately start a build pub(crate) fn start(krate: &DioxusCrate, args: BuildArgs) -> Result { let (tx, rx) = futures_channel::mpsc::unbounded(); - let request = BuildRequest::new(krate.clone(), args, tx.clone(), None); + let request = BuildRequest::new(krate.clone(), args, tx.clone(), BuildMode::Fat); Ok(Self { krate: krate.clone(), @@ -52,6 +55,7 @@ impl Builder { request.build_all().await }), + bundle: tokio::spawn(async move { futures_util::future::pending().await }), tx, rx, compiled_crates: 0, @@ -177,13 +181,26 @@ impl Builder { update } + pub(crate) fn patch_rebuild(&mut self, args: BuildArgs) { + // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit + self.abort_all(); + + // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task + let request = BuildRequest::new(self.krate.clone(), args, self.tx.clone(), BuildMode::Fat); + self.request = request.clone(); + self.stage = BuildStage::Restarting; + + // This build doesn't have any extra special logging - rebuilds would get pretty noisy + self.build = tokio::spawn(async move { request.build_all().await }); + } + /// Restart this builder with new build arguments. pub(crate) fn rebuild(&mut self, args: BuildArgs) { // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit self.abort_all(); // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task - let request = BuildRequest::new(self.krate.clone(), args, self.tx.clone(), None); + let request = BuildRequest::new(self.krate.clone(), args, self.tx.clone(), BuildMode::Fat); self.request = request.clone(); self.stage = BuildStage::Restarting; diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index 4496fcc274..c58ffd7512 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -90,14 +90,16 @@ use tokio::process::Command; pub(crate) struct AppBundle { pub(crate) build: BuildRequest, pub(crate) app: BuildArtifacts, + pub(crate) assets: AssetManifest, pub(crate) server: Option, + pub(crate) server_assets: Option, } +/// The result of the `cargo rustc` including any additional metadata #[derive(Debug)] pub struct BuildArtifacts { pub(crate) exe: PathBuf, pub(crate) direct_rustc: Vec>, - pub(crate) assets: AssetManifest, pub(crate) time_taken: Duration, } @@ -265,7 +267,13 @@ impl AppBundle { app: BuildArtifacts, server: Option, ) -> Result { - let mut bundle = Self { app, server, build }; + let mut bundle = Self { + app, + server, + build, + assets: Default::default(), + server_assets: Default::default(), + }; tracing::debug!("Assembling app bundle"); @@ -292,6 +300,31 @@ impl AppBundle { Ok(bundle) } + /// Apply this build as a patch to the given bundle + pub(crate) async fn write_patch(&mut self, exe: &Path) -> Result<()> { + Ok(()) + } + + /// Traverse the target directory and collect all assets from the incremental cache + /// + /// This uses "known paths" that have stayed relatively stable during cargo's lifetime. + /// One day this system might break and we might need to go back to using the linker approach. + pub(crate) async fn collect_assets(&self, exe: &Path) -> Result { + tracing::debug!("Collecting assets ..."); + + if self.build.build.skip_assets { + return Ok(AssetManifest::default()); + } + + // walk every file in the incremental cache dir, reading and inserting items into the manifest. + let mut manifest = AssetManifest::default(); + + // And then add from the exe directly, just in case it's LTO compiled and has no incremental cache + _ = manifest.add_from_object_path(exe); + + Ok(manifest) + } + /// Take the output of rustc and make it into the main exe of the bundle /// /// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations @@ -366,7 +399,6 @@ impl AppBundle { _ = tokio::fs::create_dir_all(&asset_dir).await; // Create a set of all the paths that new files will be bundled to let mut keep_bundled_output_paths: HashSet<_> = self - .app .assets .assets .values() @@ -426,7 +458,7 @@ impl AppBundle { let mut assets_to_transfer = vec![]; // Queue the bundled assets - for (asset, bundled) in &self.app.assets.assets { + for (asset, bundled) in &self.assets.assets { let from = asset.clone(); let to = asset_dir.join(bundled.bundled_path()); @@ -720,7 +752,6 @@ impl AppBundle { writeln!( glue, "export const __wasm_split_load_chunk_{idx} = makeLoad(\"/assets/{url}\", [], fusedImports);", url = self - .app .assets .register_asset(&path, AssetOptions::Unknown)?.bundled_path(), )?; @@ -747,7 +778,6 @@ impl AppBundle { // Again, register this wasm with the asset system url = self - .app .assets .register_asset(&path, AssetOptions::Unknown)?.bundled_path(), @@ -789,12 +819,11 @@ impl AppBundle { } // Make sure to register the main wasm file with the asset system - self.app - .assets + self.assets .register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; // Register the main.js with the asset system so it bundles in the snippets and optimizes - self.app.assets.register_asset( + self.assets.register_asset( &self.build.wasm_bindgen_js_output_file(), AssetOptions::Js(JsAssetOptions::new().with_minify(true).with_preload(true)), )?; diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 2463266250..8d24e8153b 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -31,6 +31,7 @@ pub struct PatchData { } pub async fn attempt_partial_link( + work_dir: PathBuf, old_cache: PathBuf, new_cache: PathBuf, proc_main_addr: u64, @@ -44,7 +45,7 @@ pub async fn attempt_partial_link( .new .iter() .flat_map(|(_, f)| f.file.exports().unwrap()) - .map(|e| e.name().to_utf8()) + .map(|e| e.name()) .collect::>(); let mut adrp_imports = HashSet::new(); @@ -89,13 +90,13 @@ pub async fn attempt_partial_link( .unwrap(); for i in f.file.imports().unwrap() { - if all_exports.contains(i.name().to_utf8()) { - adrp_imports.insert(i.name().to_utf8()); + if all_exports.contains(i.name()) { + adrp_imports.insert(i.name()); } } for e in f.file.exports().unwrap() { - satisfied_exports.insert(e.name().to_utf8()); + satisfied_exports.insert(e.name()); } } @@ -106,7 +107,7 @@ pub async fn attempt_partial_link( // Assemble the stub let stub_data = make_stub_file(proc_main_addr, patch_target, adrp_imports); - let stub_file = workspace_dir().join("stub.o"); + let stub_file = work_dir.join("stub.o"); std::fs::write(&stub_file, stub_data).unwrap(); let out = Command::new("cc") @@ -126,7 +127,7 @@ pub async fn attempt_partial_link( let err = String::from_utf8_lossy(&out.stderr); println!("err: {err}"); - std::fs::write(workspace_dir().join("link_errs_partial.txt"), &*err).unwrap(); + std::fs::write(work_dir.join("link_errs_partial.txt"), &*err).unwrap(); // // -O0 ? supposedly faster // // -reproducible - even better? @@ -152,11 +153,13 @@ pub async fn attempt_partial_link( // .await?; } -fn system_cc(platform: Platform) -> &'static str { +/// todo: detect if the user specified a custom linker +fn system_linker(platform: Platform) -> &'static str { match platform { + // mac + linux use just CC unless the user is trying to use something like mold / lld Platform::MacOS => "cc", - Platform::Windows => "cc", Platform::Linux => "cc", + Platform::Windows => "cc", Platform::Ios => "cc", Platform::Android => "cc", Platform::Server => "cc", @@ -618,33 +621,22 @@ fn compare_masked<'a>( fn symbol_name_of_relo<'a>(obj: &impl Object<'a>, target: RelocationTarget) -> Option<&'a str> { match target { - RelocationTarget::Symbol(symbol_index) => Some( - obj.symbol_by_index(symbol_index) - .unwrap() - .name_bytes() - .unwrap() - .to_utf8(), - ), + RelocationTarget::Symbol(symbol_index) => obj + .symbol_by_index(symbol_index) + .unwrap() + .name_bytes() + .ok() + .and_then(|s| std::str::from_utf8(s).ok()), RelocationTarget::Section(_) => None, RelocationTarget::Absolute => None, _ => None, } } -trait ToUtf8<'a> { - fn to_utf8(&self) -> &'a str; -} - -impl<'a> ToUtf8<'a> for &'a [u8] { - fn to_utf8(&self) -> &'a str { - std::str::from_utf8(self).unwrap() - } -} - fn make_stub_file( proc_main_addr: u64, patch_target: PathBuf, - adrp_imports: HashSet<&str>, + adrp_imports: HashSet<&[u8]>, ) -> Vec { let data = fs::read(&patch_target).unwrap(); let old = File::parse(&data as &[u8]).unwrap(); @@ -654,7 +646,7 @@ fn make_stub_file( .symbols() .filter_map(|sym| { adrp_imports - .get(sym.name().ok()?) + .get(sym.name_bytes().ok()?) .copied() .map(|o| (o, sym.address() + aslr_offset)) }) @@ -689,7 +681,7 @@ fn build_stub( format: BinaryFormat, architecture: Architecture, endian: Endianness, - adrp_imports: HashMap<&str, u64>, + adrp_imports: HashMap<&[u8], u64>, ) -> Result> { use object::{ write::{Object, Symbol, SymbolSection}, @@ -745,7 +737,7 @@ fn build_stub( // _$LT$generational_box..references..GenerationalRef$LT$R$GT$$u20$as$u20$core..fmt..Display$GT$::fmt::h455abb35572b9c11 // let name = strip_mangled(name); - let name = if name.starts_with("_") { + let name = if name.starts_with(b"_") { &name[1..] } else { name diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index bda7561ec1..2ce214ee0f 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -28,8 +28,27 @@ pub(crate) struct BuildRequest { /// The target directory for the build pub(crate) custom_target_dir: Option, - /// The data for the binary patch - pub(crate) patch_data: Option, + /// How we'll go about building + pub(crate) mode: BuildMode, +} + +/// dx can produce different "modes" of a build. A "regular" build is a "base" build. The Fat and Thin +/// modes are used together to achieve binary patching and linking. +#[derive(Clone, Debug)] +pub enum BuildMode { + /// A normal build generated using `cargo rustc` + Base, + + /// A "Fat" build generated with cargo rustc and dx as a custom linker without -Wl,-dead-strip + Fat, + + /// A "thin" build generated with `rustc` directly and dx as a custom linker + Thin { rustc_args: Vec }, +} + +pub struct CargoBuildResult { + rustc_args: Vec>, + exe: PathBuf, } impl BuildRequest { @@ -37,13 +56,13 @@ impl BuildRequest { krate: DioxusCrate, build: BuildArgs, progress: ProgressTx, - patch_data: Option, + mode: BuildMode, ) -> Self { Self { build, krate, progress, - patch_data, + mode, custom_target_dir: None, } } @@ -73,39 +92,17 @@ impl BuildRequest { /// Run the build command with a pretty loader, returning the executable output location async fn build_concurrent(&self) -> Result<(BuildArtifacts, Option)> { let (app, server) = - futures_util::future::try_join(self.build_app(), self.build_server()).await?; + futures_util::future::try_join(self.cargo_build(), self.build_server()).await?; Ok((app, server)) } async fn build_sequential(&self) -> Result<(BuildArtifacts, Option)> { - let app = self.build_app().await?; + let app = self.cargo_build().await?; let server = self.build_server().await?; Ok((app, server)) } - pub(crate) async fn build_app(&self) -> Result { - tracing::debug!("Building app..."); - - let start = Instant::now(); - self.prepare_build_dir()?; - - // If we're able to do a binary patch - if let Some(patch_data) = self.patch_data.as_ref() { - return self.build_cargo_with_patch(patch_data).await; - } - - let (exe, direct_rustc) = self.build_cargo().await?; - let assets = self.collect_assets(&exe).await?; - - Ok(BuildArtifacts { - exe, - direct_rustc, - assets, - time_taken: start.elapsed(), - }) - } - pub(crate) async fn build_server(&self) -> Result> { tracing::debug!("Building server..."); @@ -115,59 +112,30 @@ impl BuildRequest { let mut cloned = self.clone(); cloned.build.platform = Some(Platform::Server); - Ok(Some(cloned.build_app().await?)) + + Ok(Some(cloned.cargo_build().await?)) } - /// Run `cargo`, returning the location of the final executable - /// - /// todo: add some stats here, like timing reports, crate-graph optimizations, etc - pub(crate) async fn build_cargo(&self) -> Result<(PathBuf, Vec>)> { + pub(crate) async fn cargo_build(&self) -> Result { + let start = Instant::now(); + self.prepare_build_dir()?; + tracing::debug!("Executing cargo..."); + let mut cmd = self.assemble_build_command()?; + + tracing::trace!(dx_src = ?TraceSrc::Build, "Rust cargo args: {:#?}", cmd); + // Extract the unit count of the crate graph so build_cargo has more accurate data - let crate_count = self.get_unit_count_estimate().await; + // "Thin" builds only build the final exe, so we only need to build one crate + let crate_count = match self.mode { + BuildMode::Thin { .. } => 1, + _ => self.get_unit_count_estimate().await, + }; // Update the status to show that we're starting the build and how many crates we expect to build self.status_starting_build(crate_count); - let mut cmd = Command::new("cargo"); - - cmd.arg("rustc") - .current_dir(self.krate.crate_dir()) - .arg("--message-format") - .arg("json-diagnostic-rendered-ansi") - .args(self.build_arguments()) - .envs(self.env_vars()?); - - if let Some(target_dir) = self.custom_target_dir.as_ref() { - cmd.env("CARGO_TARGET_DIR", target_dir); - } - - // Android needs a special linker since the linker is actually tied to the android toolchain. - // For the sake of simplicity, we're going to pass the linker here using ourselves as the linker, - // but in reality we could simply use the android toolchain's linker as the path. - // - // We don't want to overwrite the user's .cargo/config.toml since that gets committed to git - // and we want everyone's install to be the same. - if self.build.platform() == Platform::Android { - let ndk = self - .krate - .android_ndk() - .context("Could not autodetect android linker")?; - let arch = self.build.target_args.arch(); - let linker = arch.android_linker(&ndk); - - let link_action = LinkAction::LinkAndroid { - linker, - extra_flags: vec![], - } - .to_json(); - - cmd.env(LinkAction::ENV_VAR_NAME, link_action); - } - - tracing::trace!(dx_src = ?TraceSrc::Build, "Rust cargo args: {:#?}", cmd); - let mut child = cmd .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -176,7 +144,7 @@ impl BuildRequest { let stdout = tokio::io::BufReader::new(child.stdout.take().unwrap()); let stderr = tokio::io::BufReader::new(child.stderr.take().unwrap()); - let mut output_location = None; + let mut output_location: Option = None; let mut stdout = stdout.lines(); let mut stderr = stderr.lines(); let mut units_compiled = 0; @@ -254,34 +222,73 @@ impl BuildRequest { tracing::error!("Cargo build failed - no output location. Toggle tracing mode (press `t`) for more information."); } - let out_location = output_location.context("Build did not return an executable")?; + let exe = output_location.context("Build did not return an executable")?; - tracing::debug!( - "Build completed successfully - output location: {:?}", - out_location - ); + tracing::debug!("Build completed successfully - output location: {:?}", exe); - Ok(out_location) + Ok(BuildArtifacts { + exe, + direct_rustc, + time_taken: start.elapsed(), + }) } - /// Traverse the target directory and collect all assets from the incremental cache - /// - /// This uses "known paths" that have stayed relatively stable during cargo's lifetime. - /// One day this system might break and we might need to go back to using the linker approach. - pub(crate) async fn collect_assets(&self, exe: &Path) -> Result { - tracing::debug!("Collecting assets ..."); + pub(crate) async fn build_thin_rustc(&self) {} + + // #[tracing::instrument( + // skip(self), + // level = "trace", + // name = "BuildRequest::assemble_build_command" + // )] + fn assemble_build_command(&self) -> Result { + let mut cmd = match &self.mode { + BuildMode::Fat | BuildMode::Base => { + let mut cmd = Command::new("cargo"); + cmd.arg("rustc") + .current_dir(self.krate.crate_dir()) + .arg("--message-format") + .arg("json-diagnostic-rendered-ansi") + .args(self.build_arguments()) + .envs(self.env_vars()?); + cmd + } + BuildMode::Thin { rustc_args } => { + let mut cmd = Command::new(rustc_args[0].clone()); + cmd.args(rustc_args[1..].iter()) + .env( + LinkAction::ENV_VAR_NAME, + LinkAction::FatLink { + platform: self.build.platform(), + linker: None, + incremental_dir: self.incremental_cache_dir(), + } + .to_json(), + ) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + cmd + } + }; - if self.build.skip_assets { - return Ok(AssetManifest::default()); + if let Some(target_dir) = self.custom_target_dir.as_ref() { + cmd.env("CARGO_TARGET_DIR", target_dir); } - // walk every file in the incremental cache dir, reading and inserting items into the manifest. - let mut manifest = AssetManifest::default(); + if self.build.platform() == Platform::Android { + let ndk = self + .krate + .android_ndk() + .context("Could not autodetect android linker")?; + let arch = self.build.target_args.arch(); + let linker = arch.android_linker(&ndk); - // And then add from the exe directly, just in case it's LTO compiled and has no incremental cache - _ = manifest.add_from_object_path(exe); + cmd.env( + LinkAction::ENV_VAR_NAME, + LinkAction::LinkAndroid { linker }.to_json(), + ); + } - Ok(manifest) + Ok(cmd) } /// Create a list of arguments for cargo builds @@ -603,6 +610,10 @@ impl BuildRequest { Ok(()) } + pub fn incremental_cache_dir(&self) -> PathBuf { + self.platform_dir().join("incremental-cache") + } + /// The directory in which we'll put the main exe /// /// Mac, Android, Web are a little weird diff --git a/packages/cli/src/build/web.rs b/packages/cli/src/build/web.rs index 52b2e8e914..a809754566 100644 --- a/packages/cli/src/build/web.rs +++ b/packages/cli/src/build/web.rs @@ -85,7 +85,7 @@ impl AppBundle { } // Inject any resources from manganis into the head - for asset in self.app.assets.assets.values() { + for asset in self.assets.assets.values() { let asset_path = asset.bundled_path(); match asset.options() { AssetOptions::Css(css_options) => { @@ -115,7 +115,6 @@ impl AppBundle { // Manually inject the wasm file for preloading. WASM currently doesn't support preloading in the manganis asset system let wasm_source_path = self.build.wasm_bindgen_wasm_output_file(); let wasm_path = self - .app .assets .assets .get(&wasm_source_path) @@ -175,7 +174,6 @@ r#" + + +

+ + diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs index b4e9e01228..84d39d6958 100644 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ b/packages/subsecond/subsecond-cli/src/main.rs @@ -1,16 +1,17 @@ use anyhow::Context; use cargo_metadata::camino::Utf8PathBuf; +use clap::Parser; use futures::{SinkExt, StreamExt}; use itertools::Itertools; use notify::{ event::{DataChange, ModifyKind}, Watcher, }; -use object::write::Object; +use object::{write::Object, Architecture}; use serde::Deserialize; use std::{collections::HashMap, env, ffi::OsStr, path::PathBuf, process::Stdio, time::SystemTime}; use subsecond_cli_support::create_jump_table; -use target_lexicon::Triple; +use target_lexicon::{Environment, Triple}; use tokio::{ io::AsyncBufReadExt, net::TcpListener, @@ -29,6 +30,12 @@ async fn main() -> anyhow::Result<()> { hotreload_loop().await } +#[derive(Debug, Parser)] +struct Args { + #[clap(long)] + target: Option, +} + /// The main loop of the hotreload process /// /// 1. Create initial "fat" build @@ -40,6 +47,12 @@ async fn main() -> anyhow::Result<()> { /// 7. Pause the process with lldb, run the "hotfn_load_binary_patch" command and then continue /// 8. Repeat async fn hotreload_loop() -> anyhow::Result<()> { + let args = Args::parse(); + let target: Triple = args + .target + .map(|t| t.parse().unwrap()) + .unwrap_or_else(|| Triple::host()); + // Save the state of the rust files let src_folder = subsecond_folder().join("subsecond-harness/src/"); let main_rs = src_folder.join("main.rs"); @@ -51,8 +64,8 @@ async fn hotreload_loop() -> anyhow::Result<()> { // Perform the initial build let epoch = SystemTime::UNIX_EPOCH; let now = std::time::Instant::now(); - println!("Starting build..."); - let result = initial_build().await?; + println!("Starting build for target {target:?}..."); + let result = initial_build(&target).await?; println!( "Initial build: {:?} -> {}", now.elapsed(), @@ -68,13 +81,7 @@ async fn hotreload_loop() -> anyhow::Result<()> { std::fs::copy(&exe, &fat_exe).unwrap(); // Launch the fat exe. We'll overwrite the slim exe location, so this prevents the app from bugging out - let app = Command::new(&fat_exe) - .kill_on_drop(true) - .env( - "ASLR_FILE", - subsecond_folder().join("data").join("aslr.txt"), - ) - .spawn()?; + let app = launch_app(&fat_exe, &target)?; // Wait for the websocket to come up let mut client = wait_for_ws(9393).await?; @@ -99,7 +106,7 @@ async fn hotreload_loop() -> anyhow::Result<()> { } let started = Instant::now(); - let Ok(output_temp) = fast_build(&result, client.aslr_reference).await else { + let Ok(output_temp) = fast_build(&result, &target, client.aslr_reference).await else { continue; }; @@ -129,6 +136,23 @@ async fn hotreload_loop() -> anyhow::Result<()> { Ok(()) } +fn launch_app(fat_exe: &Utf8PathBuf, target: &Triple) -> Result { + let app = match target.architecture { + target_lexicon::Architecture::Wasm32 => Command::new("python3") + .current_dir(static_folder()) + .arg("-m") + .arg("http.server") + .arg("9394") + .arg("--directory") + .arg(".") + .kill_on_drop(true) + .spawn()?, + _ => Command::new(fat_exe).kill_on_drop(true).spawn()?, + }; + + Ok(app) +} + struct FsWatcher { _watcher: notify::RecommendedWatcher, files: HashMap, @@ -229,12 +253,14 @@ async fn link(action: String) -> anyhow::Result<()> { Ok(()) } -async fn initial_build() -> anyhow::Result { +async fn initial_build(target: &Triple) -> anyhow::Result { // Perform the initial build and print out the link arguments. Don't strip dead code and preserve temp files. // This results in a "fat" executable that we can bind to // // todo: clean up the temps manually - let inital_build = Command::new("cargo") + let mut build = Command::new("cargo"); + + build .arg("rustc") .arg("--package") .arg("subsecond-harness") @@ -245,26 +271,83 @@ async fn initial_build() -> anyhow::Result { .arg("--message-format") .arg("json-diagnostic-rendered-ansi") .arg("--verbose") + .arg("--target") + .arg(target.to_string()); + + match target.architecture { + target_lexicon::Architecture::Wasm32 => { + build.arg("--features").arg("web"); + } + _ => {} + } + + // these args are required to prevent DCE, save intermediates, and print the link args for future usage + // -all_load ensures all statics get bubbled out + // -link-dead-code prevents the flag `-Wl,-dead_strip` from being passed + // -save-temps ensures the intermediates are saved so we can use them for comparsions + build .arg("--") - // these args are required to prevent DCE, save intermediates, and print the link args for future usage - // -all_load ensures all statics get bubbled out - // -link-dead-code prevents the flag `-Wl,-dead_strip` from being passed - // -save-temps ensures the intermediates are saved so we can use them for comparsions - // - // todo: delete the temps .arg("-Csave-temps=true") - .arg("-Clink-dead-code") - .arg("-Clink-arg=-Wl,-all_load") - // we capture the link args, but eventually we should actually just use ourselves as the linker since that's more robust + .arg("-Clink-dead-code"); + + match target.architecture { + // usually just ld64 - uses your `cc` + target_lexicon::Architecture::Aarch64(_) => { + build.arg("-Clink-arg=-Wl,-all_load"); + } + + // /Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld + target_lexicon::Architecture::Wasm32 => { + // we want "all-load", adjustable ifunc table, + build.arg("-Clink-arg=--no-gc-sections"); + build.arg("-Clink-arg=--growable-table"); + build.arg("-Clink-arg=--whole-archive"); + // build.arg("-Clink-arg=--export-dynamic"); + } + _ => {} + } + + // we capture the link args, but eventually we should actually just use ourselves as the linker since that's more robust + build .arg("--print") .arg("link-args") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true) - .current_dir(workspace_dir()) - .spawn()?; + .current_dir(workspace_dir()); + + let build = build.spawn()?; + + let out = run_cargo_output(build, rust_log_enabled()).await?; + + if target.architecture == target_lexicon::Architecture::Wasm32 { + std::fs::remove_dir_all(static_folder()).unwrap(); + + let bind = Command::new("wasm-bindgen") + .arg("--target") + .arg("web") + .arg("--no-typescript") + .arg("--out-dir") + .arg(static_folder()) + .arg("--out-name") + .arg("main") + .arg(out.output_location.as_std_path()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true) + .current_dir(workspace_dir()) + .output() + .await?; + + let index = include_str!("./index.html"); + std::fs::write(static_folder().join("index.html"), index).unwrap(); + } - run_cargo_output(inital_build, rust_log_enabled()).await + Ok(out) +} + +fn static_folder() -> PathBuf { + subsecond_folder().join("subsecond-harness").join("static") } fn rust_log_enabled() -> bool { @@ -276,6 +359,7 @@ fn rust_log_enabled() -> bool { async fn fast_build( original: &CargoOutputResult, + target: &Triple, aslr_reference: u64, ) -> anyhow::Result { let fast_build = Command::new(original.direct_rustc[0].clone()) @@ -317,20 +401,42 @@ async fn fast_build( SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() )); - // todo: we should throw out symbols that we don't need and/or assemble them manually - let res = Command::new("cc") - .args(object_files) - .arg("-Wl,-dylib") - // .arg("-Wl,-undefined,dynamic_lookup") - // .arg("-Wl,-export_dynamic") - .arg("-arch") - .arg("arm64") - .arg("-o") - .arg(&output_location) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await?; + let res = match target.architecture { + // usually just ld64 - uses your `cc` + target_lexicon::Architecture::Aarch64(_) => { + // todo: we should throw out symbols that we don't need and/or assemble them manually + Command::new("cc") + .args(object_files) + .arg("-Wl,-dylib") + // .arg("-Wl,-undefined,dynamic_lookup") + // .arg("-Wl,-export_dynamic") + .arg("-arch") + .arg("arm64") + .arg("-o") + .arg(&output_location) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await? + } + target_lexicon::Architecture::Wasm32 => { + let ld = wasm_ld().await?; + // --import-memory Import the module's memory from the default module of "env" with the name "memory". + // --import-table Import function table from the environment + Command::new(ld) + .args(object_files) + .arg("-Wl,--import-memory") + .arg("-Wl,--import-table") + .arg("-Wl,--growable-table") + .arg("-o") + .arg(&output_location) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await? + } + _ => todo!(), + }; let errs = String::from_utf8_lossy(&res.stderr); if !errs.is_empty() { @@ -447,6 +553,17 @@ async fn run_cargo_output( }) } +async fn wasm_ld() -> anyhow::Result { + let root = Command::new("rustc") + .arg("--print") + .arg("--sysroot") + .output() + .await?; + let root = String::from_utf8(root.stdout)?; + let root = PathBuf::from(root.trim()); + Ok(root.join("lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld")) +} + fn workspace_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../../") @@ -461,28 +578,3 @@ fn subsecond_folder() -> PathBuf { .canonicalize() .unwrap() } - -/// Move all previous object files to "incremental-old" and all new object files to "incremental-new" -fn cache_incrementals(object_files: &[&String]) { - let old = subsecond_folder().join("data").join("incremental-old"); - let new = subsecond_folder().join("data").join("incremental-new"); - - // Remove the old incremental-old directory if it exists - _ = std::fs::remove_dir_all(&old); - - // Rename incremental-new to incremental-old if it exists. Faster than moving all the files - _ = std::fs::rename(&new, &old); - - // Create the new incremental-new directory to place the outputs in - std::fs::create_dir_all(&new).unwrap(); - - // Now drop in all the new object files - for o in object_files.iter() { - if !o.ends_with(".rcgu.o") { - continue; - } - - let path = PathBuf::from(o); - std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); - } -} From c11d76ca539a3113626f65737083ab4b368ef1f4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 22 Mar 2025 01:37:14 -0700 Subject: [PATCH 031/301] wip: wasm --- Cargo.lock | 1 + .../subsecond-cli-support/src/resolve.rs | 26 +++-- packages/subsecond/subsecond-cli/Cargo.toml | 1 + packages/subsecond/subsecond-cli/src/main.rs | 107 +++++++++++------- .../subsecond-harness/src/dioxus_demo.rs | 2 +- 5 files changed, 82 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32f6c158b3..5eaa7e8b6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12347,6 +12347,7 @@ dependencies = [ "tokio", "tokio-tungstenite 0.23.1", "tracing", + "tracing-subscriber", "urlencoding", "walkdir", ] diff --git a/packages/subsecond/subsecond-cli-support/src/resolve.rs b/packages/subsecond/subsecond-cli-support/src/resolve.rs index 1ce7a23361..29d12736cc 100644 --- a/packages/subsecond/subsecond-cli-support/src/resolve.rs +++ b/packages/subsecond/subsecond-cli-support/src/resolve.rs @@ -29,7 +29,7 @@ pub fn resolve_undefined( source: &Path, incrementals: &[PathBuf], triple: &Triple, - aslr_reference: u64, + aslr_reference: Option, ) -> Result> { let sorted: Vec<_> = incrementals.iter().sorted().collect(); @@ -111,15 +111,21 @@ pub fn resolve_undefined( .collect::>(); // Get the offset from the main module - let aslr_offset = aslr_reference - - symbol_table - .get("_aslr_reference") - .unwrap_or_else(|| { - symbol_table - .get("aslr_reference") - .expect("failed to find aslr_reference") - }) - .address(); + let aslr_offset = match triple.architecture { + target_lexicon::Architecture::Wasm32 => 0, + _ => { + let aslr_reference = aslr_reference.unwrap(); + aslr_reference + - symbol_table + .get("_aslr_reference") + .unwrap_or_else(|| { + symbol_table + .get("aslr_reference") + .expect("failed to find aslr_reference") + }) + .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 diff --git a/packages/subsecond/subsecond-cli/Cargo.toml b/packages/subsecond/subsecond-cli/Cargo.toml index 90fc911f55..29fb08f62c 100644 --- a/packages/subsecond/subsecond-cli/Cargo.toml +++ b/packages/subsecond/subsecond-cli/Cargo.toml @@ -38,3 +38,4 @@ tokio-tungstenite = "0.23.0" crossterm = { workspace = true } subsecond-cli-support = { path = "../subsecond-cli-support" } target-lexicon = "0.13.2" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs index 84d39d6958..9049318003 100644 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ b/packages/subsecond/subsecond-cli/src/main.rs @@ -19,6 +19,7 @@ use tokio::{ time::Instant, }; use tokio_tungstenite::WebSocketStream; +use tracing::info; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -47,6 +48,8 @@ struct Args { /// 7. Pause the process with lldb, run the "hotfn_load_binary_patch" command and then continue /// 8. Repeat async fn hotreload_loop() -> anyhow::Result<()> { + tracing_subscriber::fmt::init(); + let args = Args::parse(); let target: Triple = args .target @@ -84,7 +87,7 @@ async fn hotreload_loop() -> anyhow::Result<()> { let app = launch_app(&fat_exe, &target)?; // Wait for the websocket to come up - let mut client = wait_for_ws(9393).await?; + let mut client = wait_for_ws(9393, &target).await?; // don't log if the screen has been taken over - important for tui apps let should_log = rust_log_enabled(); @@ -101,12 +104,12 @@ async fn hotreload_loop() -> anyhow::Result<()> { continue; } - if should_log { - println!("Fast reloading... "); - } + tracing::info!("Fast reloading... "); let started = Instant::now(); - let Ok(output_temp) = fast_build(&result, &target, client.aslr_reference).await else { + let Ok(output_temp) = + fast_build(&result, &target, client.as_ref().map(|s| s.aslr_reference)).await + else { continue; }; @@ -119,16 +122,16 @@ async fn hotreload_loop() -> anyhow::Result<()> { ) .unwrap(); - client - .socket - .send(tokio_tungstenite::tungstenite::Message::Binary( - bincode::serialize(&jump_table).unwrap().into(), - )) - .await?; - - if should_log { - println!("Patching complete in {}ms", started.elapsed().as_millis()) + if let Some(client) = client.as_mut() { + client + .socket + .send(tokio_tungstenite::tungstenite::Message::Binary( + bincode::serialize(&jump_table).unwrap().into(), + )) + .await?; } + + tracing::info!("Patching complete in {}ms", started.elapsed().as_millis()) } drop(app); @@ -138,15 +141,18 @@ async fn hotreload_loop() -> anyhow::Result<()> { fn launch_app(fat_exe: &Utf8PathBuf, target: &Triple) -> Result { let app = match target.architecture { - target_lexicon::Architecture::Wasm32 => Command::new("python3") - .current_dir(static_folder()) - .arg("-m") - .arg("http.server") - .arg("9394") - .arg("--directory") - .arg(".") - .kill_on_drop(true) - .spawn()?, + target_lexicon::Architecture::Wasm32 => { + info!("Serving wasm at http://127.0.0.1:9393"); + Command::new("python3") + .current_dir(static_folder()) + .arg("-m") + .arg("http.server") + .arg("9394") + .arg("--directory") + .arg(".") + .kill_on_drop(true) + .spawn()? + } _ => Command::new(fat_exe).kill_on_drop(true).spawn()?, }; @@ -205,19 +211,24 @@ struct WsClient { socket: WebSocketStream, } -async fn wait_for_ws(port: u16) -> anyhow::Result { - let port = port; +async fn wait_for_ws(port: u16, target: &Triple) -> anyhow::Result> { + if target.architecture == target_lexicon::Architecture::Wasm32 { + return Ok(None); + } + let addr = format!("127.0.0.1:{}", port); let try_socket = TcpListener::bind(&addr).await; let listener = try_socket.expect("Failed to bind"); + let (conn, _sock) = listener.accept().await?; let mut socket = tokio_tungstenite::accept_async(conn).await?; let msg = socket.next().await.unwrap()?; let aslr_reference = msg.into_text().unwrap().parse().unwrap(); - Ok(WsClient { + + Ok(Some(WsClient { aslr_reference, socket, - }) + })) } /// Store the linker args in a file for the main process to read. @@ -225,10 +236,7 @@ async fn link(action: String) -> anyhow::Result<()> { let args = std::env::args().collect::>(); // Write the linker args to a file for the main process to read - std::fs::write( - subsecond_folder().join("data").join("link.txt"), - args.join("\n"), - )?; + std::fs::write(link_args_file(), args.join("\n"))?; match action.as_str() { // Actually link the object file. todo: figure out which linker we should be using @@ -253,6 +261,10 @@ async fn link(action: String) -> anyhow::Result<()> { Ok(()) } +fn link_args_file() -> PathBuf { + subsecond_folder().join("data").join("link.txt") +} + async fn initial_build(target: &Triple) -> anyhow::Result { // Perform the initial build and print out the link arguments. Don't strip dead code and preserve temp files. // This results in a "fat" executable that we can bind to @@ -302,6 +314,7 @@ async fn initial_build(target: &Triple) -> anyhow::Result { build.arg("-Clink-arg=--no-gc-sections"); build.arg("-Clink-arg=--growable-table"); build.arg("-Clink-arg=--whole-archive"); + // build.arg("-Clink-arg=--export-all"); // build.arg("-Clink-arg=--export-dynamic"); } _ => {} @@ -331,6 +344,7 @@ async fn initial_build(target: &Triple) -> anyhow::Result { .arg(static_folder()) .arg("--out-name") .arg("main") + .arg("--no-demangle") .arg(out.output_location.as_std_path()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -360,7 +374,7 @@ fn rust_log_enabled() -> bool { async fn fast_build( original: &CargoOutputResult, target: &Triple, - aslr_reference: u64, + aslr_reference: Option, ) -> anyhow::Result { let fast_build = Command::new(original.direct_rustc[0].clone()) .args(original.direct_rustc[1..].iter()) @@ -377,18 +391,22 @@ async fn fast_build( let output = run_cargo_output(fast_build, rust_log_enabled()).await?; - let mut object_files = output - .link_args - .iter() + tracing::info!("fast_build output: {output:#?}"); + + let link_args = std::fs::read_to_string(link_args_file())?; + let mut object_files = link_args + .lines() .filter(|arg| arg.ends_with(".rcgu.o")) .sorted() .map(|arg| PathBuf::from(arg)) .collect::>(); + tracing::info!("object_files: {object_files:#?}"); + let resolved = subsecond_cli_support::resolve::resolve_undefined( &original.output_location.as_std_path(), &object_files, - &Triple::host(), + target, aslr_reference, ) .unwrap(); @@ -440,12 +458,13 @@ async fn fast_build( let errs = String::from_utf8_lossy(&res.stderr); if !errs.is_empty() { - println!("errs: {errs}"); + tracing::error!("errs: {errs}"); } Ok(output_location) } +#[derive(Debug)] struct CargoOutputResult { output_location: Utf8PathBuf, direct_rustc: Vec, @@ -481,7 +500,7 @@ async fn run_cargo_output( Some(Ok(message)) => message, None => break, other => { - println!("other: {other:?}"); + // println!("other: {other:?}"); break; } }; @@ -494,9 +513,9 @@ async fn run_cargo_output( } Message::CompilerMessage(compiler_message) => { if let Some(rendered) = &compiler_message.message.rendered { - if should_render { - println!("rendered: {rendered}"); - } + // if should_render { + // println!("rendered: {rendered}"); + // } } } Message::BuildScriptExecuted(_build_script) => {} @@ -534,9 +553,9 @@ async fn run_cargo_output( } } - if should_render { - println!("text: {word}") - } + // if should_render { + // println!("text: {word}") + // } } _ => {} } diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs index 190b9469ed..4e9bfb5928 100644 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs @@ -10,7 +10,7 @@ fn app() -> Element { rsx! { "hi {count}" div { - for x in 0..7 { + for x in 0..2 { Child { id: x, opt: "List entry" } } } From 6eec64337fd872f948bf57a75b3dd7a3d3a45025 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 23 Mar 2025 02:35:42 -0700 Subject: [PATCH 032/301] wasm is very very close --- Cargo.lock | 12 + packages/document/src/js/hash.txt | 2 +- packages/document/src/ts/eval.ts | 8 +- packages/interpreter/src/js/common.js | 86 +++- packages/interpreter/src/js/core.js | 266 +++++++++- packages/interpreter/src/lib.rs | 2 +- packages/logger/src/lib.rs | 1 + .../subsecond-cli-support/Cargo.toml | 9 + .../subsecond-cli-support/src/lib.rs | 1 + .../subsecond-cli-support/src/resolve.rs | 12 +- .../subsecond-cli-support/src/wasm-incrs.txt | 98 ++++ .../subsecond-cli-support/src/wasm.rs | 456 ++++++++++++++++++ packages/subsecond/subsecond-cli/Cargo.toml | 4 + .../subsecond/subsecond-cli/src/index.html | 62 ++- packages/subsecond/subsecond-cli/src/main.rs | 172 +++++-- packages/subsecond/subsecond-cli/src/patch.js | 41 ++ packages/subsecond/subsecond-cli/src/patch.ts | 32 ++ .../subsecond-harness/src/dioxus_demo.rs | 33 +- packages/subsecond/subsecond/Cargo.toml | 3 + packages/subsecond/subsecond/src/wasm.rs | 21 + packages/web/src/document.rs | 17 +- packages/web/src/js/eval.js | 2 +- packages/web/src/js/hash.txt | 2 +- packages/web/src/ts/eval.ts | 1 + 24 files changed, 1273 insertions(+), 70 deletions(-) create mode 100644 packages/subsecond/subsecond-cli-support/src/wasm-incrs.txt create mode 100644 packages/subsecond/subsecond-cli-support/src/wasm.rs create mode 100644 packages/subsecond/subsecond-cli/src/patch.js create mode 100644 packages/subsecond/subsecond-cli/src/patch.ts diff --git a/Cargo.lock b/Cargo.lock index 5eaa7e8b6d..5dc7692318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12304,12 +12304,14 @@ dependencies = [ name = "subsecond" version = "0.6.3" dependencies = [ + "js-sys", "libc", "libloading 0.8.6", "memmap", "serde", "subsecond-macro", "subsecond-types", + "wasm-bindgen", ] [[package]] @@ -12317,6 +12319,9 @@ name = "subsecond-cli" version = "0.6.3" dependencies = [ "anyhow", + "axum 0.7.9", + "axum-extra", + "axum-server", "bincode", "cargo_metadata 0.19.2", "clap", @@ -12346,6 +12351,7 @@ dependencies = [ "target-lexicon 0.13.2", "tokio", "tokio-tungstenite 0.23.1", + "tower-http", "tracing", "tracing-subscriber", "urlencoding", @@ -12358,18 +12364,24 @@ version = "0.6.3" dependencies = [ "anyhow", "bincode", + "clap", + "id-arena", "itertools 0.14.0", "libc", "memmap", "object 0.36.5", "pretty-hex", "pretty_assertions", + "rayon", "rustc-demangle", "serde", "subsecond-types", "target-lexicon 0.13.2", "tokio", "tracing", + "tracing-subscriber", + "walrus", + "wasmparser 0.225.0", ] [[package]] diff --git a/packages/document/src/js/hash.txt b/packages/document/src/js/hash.txt index faaeb89f2e..72deb92d11 100644 --- a/packages/document/src/js/hash.txt +++ b/packages/document/src/js/hash.txt @@ -1 +1 @@ -[206827801705263822, 8375185156499858125] \ No newline at end of file +[3101459268373648704, 8375185156499858125] \ No newline at end of file diff --git a/packages/document/src/ts/eval.ts b/packages/document/src/ts/eval.ts index 4440fe9dfd..df3e972414 100644 --- a/packages/document/src/ts/eval.ts +++ b/packages/document/src/ts/eval.ts @@ -1,6 +1,6 @@ // Handle communication between rust and evaluating javascript -export class Channel { +class Channel { pending: any[]; waiting: ((data: any) => void)[]; @@ -32,7 +32,7 @@ export class Channel { } } -export class WeakDioxusChannel { +class WeakDioxusChannel { inner: WeakRef; constructor(channel: DioxusChannel) { @@ -56,7 +56,7 @@ export class WeakDioxusChannel { } } -export abstract class DioxusChannel { +abstract class DioxusChannel { // Return a weak reference to this channel weak(): WeakDioxusChannel { return new WeakDioxusChannel(this); @@ -68,3 +68,5 @@ export abstract class DioxusChannel { // Receive data sent from javascript in rust abstract rustRecv(): Promise; } + +export { DioxusChannel, Channel, WeakDioxusChannel }; diff --git a/packages/interpreter/src/js/common.js b/packages/interpreter/src/js/common.js index 6a4c74cf6d..f5d927f4a2 100644 --- a/packages/interpreter/src/js/common.js +++ b/packages/interpreter/src/js/common.js @@ -1 +1,85 @@ -function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}function truthy(val){return val==="true"||val===!0}function isBoolAttr(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}}function retrieveFormValues(form){let formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}export{setAttributeInner,retrieveFormValues}; +function setAttributeInner(node, field, value, ns) { + if (ns === "style") { + node.style.setProperty(field, value); + return; + } + if (ns) { + node.setAttributeNS(ns, field, value); + return; + } + switch (field) { + case "value": + if (node.value !== value) node.value = value; + break; + case "initial_value": + node.defaultValue = value; + break; + case "checked": + node.checked = truthy(value); + break; + case "initial_checked": + node.defaultChecked = truthy(value); + break; + case "selected": + node.selected = truthy(value); + break; + case "initial_selected": + node.defaultSelected = truthy(value); + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + if (!truthy(value) && isBoolAttr(field)) node.removeAttribute(field); + else node.setAttribute(field, value); + } +} +function truthy(val) { + return val === "true" || val === !0; +} +function isBoolAttr(field) { + switch (field) { + case "allowfullscreen": + case "allowpaymentrequest": + case "async": + case "autofocus": + case "autoplay": + case "checked": + case "controls": + case "default": + case "defer": + case "disabled": + case "formnovalidate": + case "hidden": + case "ismap": + case "itemscope": + case "loop": + case "multiple": + case "muted": + case "nomodule": + case "novalidate": + case "open": + case "playsinline": + case "readonly": + case "required": + case "reversed": + case "selected": + case "truespeed": + case "webkitdirectory": + return !0; + default: + return !1; + } +} +function retrieveFormValues(form) { + let formData = new FormData(form), + contents = {}; + return ( + formData.forEach((value, key) => { + if (contents[key]) contents[key].push(value); + else contents[key] = [value]; + }), + { valid: form.checkValidity(), values: contents } + ); +} +export { setAttributeInner, retrieveFormValues }; diff --git a/packages/interpreter/src/js/core.js b/packages/interpreter/src/js/core.js index 0487dbc270..86f65ad576 100644 --- a/packages/interpreter/src/js/core.js +++ b/packages/interpreter/src/js/core.js @@ -1 +1,265 @@ -function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;default:if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}}function truthy(val){return val==="true"||val===!0}function isBoolAttr(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}}class BaseInterpreter{global;local;root;handler;resizeObserver;intersectionObserver;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},this.handler=handler,root.setAttribute("data-dioxus-id","0")}handleResizeEvent(entry){let target=entry.target,event=new CustomEvent("resize",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createResizeObserver(element){if(!this.resizeObserver)this.resizeObserver=new ResizeObserver((entries)=>{for(let entry of entries)this.handleResizeEvent(entry)});this.resizeObserver.observe(element)}removeResizeObserver(element){if(this.resizeObserver)this.resizeObserver.unobserve(element)}handleIntersectionEvent(entry){let target=entry.target,event=new CustomEvent("visible",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createIntersectionObserver(element){if(!this.intersectionObserver)this.intersectionObserver=new IntersectionObserver((entries)=>{for(let entry of entries)this.handleIntersectionEvent(entry)});this.intersectionObserver.observe(element)}removeIntersectionObserver(element){if(this.intersectionObserver)this.intersectionObserver.unobserve(element)}createListener(event_name,element,bubbles){if(event_name=="resize")this.createResizeObserver(element);else if(event_name=="visible")this.createIntersectionObserver(element);if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{let id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}}removeListener(element,event_name,bubbles){if(event_name=="resize")this.removeResizeObserver(element);else if(event_name=="visible")this.removeIntersectionObserver(element);else if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){let id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){let id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){let root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){let split=hydrateNode.getAttribute("data-node-hydration").split(","),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j{if(!treeWalker.nextNode())return!1;return treeWalker.currentNode!==nextSibling};while(treeWalker.currentNode){let currentNode=treeWalker.currentNode;if(currentNode.nodeType===Node.COMMENT_NODE){let id=currentNode.textContent,placeholderSplit=id.split("placeholder");if(placeholderSplit.length>1){if(this.nodes[ids[parseInt(placeholderSplit[1])]]=currentNode,!continueToNextNode())break;continue}let textNodeSplit=id.split("node-id");if(textNodeSplit.length>1){let next=currentNode.nextSibling;currentNode.remove();let commentAfterText,textNode;if(next.nodeType===Node.COMMENT_NODE){let newText=next.parentElement.insertBefore(document.createTextNode(""),next);commentAfterText=next,textNode=newText}else textNode=next,commentAfterText=textNode.nextSibling;treeWalker.currentNode=commentAfterText,this.nodes[ids[parseInt(textNodeSplit[1])]]=textNode;let exit=currentNode===under||!continueToNextNode();if(commentAfterText.remove(),exit)break;continue}}if(!continueToNextNode())break}}}setAttributeInner(node,field,value,ns){setAttributeInner(node,field,value,ns)}}export{BaseInterpreter}; +function setAttributeInner(node, field, value, ns) { + if (ns === "style") { + node.style.setProperty(field, value); + return; + } + if (ns) { + node.setAttributeNS(ns, field, value); + return; + } + switch (field) { + case "value": + if (node.value !== value) node.value = value; + break; + case "initial_value": + node.defaultValue = value; + break; + case "checked": + node.checked = truthy(value); + break; + case "initial_checked": + node.defaultChecked = truthy(value); + break; + case "selected": + node.selected = truthy(value); + break; + case "initial_selected": + node.defaultSelected = truthy(value); + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + if (!truthy(value) && isBoolAttr(field)) node.removeAttribute(field); + else node.setAttribute(field, value); + } +} +function truthy(val) { + return val === "true" || val === !0; +} +function isBoolAttr(field) { + switch (field) { + case "allowfullscreen": + case "allowpaymentrequest": + case "async": + case "autofocus": + case "autoplay": + case "checked": + case "controls": + case "default": + case "defer": + case "disabled": + case "formnovalidate": + case "hidden": + case "ismap": + case "itemscope": + case "loop": + case "multiple": + case "muted": + case "nomodule": + case "novalidate": + case "open": + case "playsinline": + case "readonly": + case "required": + case "reversed": + case "selected": + case "truespeed": + case "webkitdirectory": + return !0; + default: + return !1; + } +} +class BaseInterpreter { + global; + local; + root; + handler; + resizeObserver; + intersectionObserver; + nodes; + stack; + templates; + m; + constructor() {} + initialize(root, handler = null) { + (this.global = {}), + (this.local = {}), + (this.root = root), + (this.nodes = [root]), + (this.stack = [root]), + (this.templates = {}), + (this.handler = handler), + root.setAttribute("data-dioxus-id", "0"); + } + handleResizeEvent(entry) { + let target = entry.target, + event = new CustomEvent("resize", { bubbles: !1, detail: entry }); + target.dispatchEvent(event); + } + createResizeObserver(element) { + if (!this.resizeObserver) + this.resizeObserver = new ResizeObserver((entries) => { + for (let entry of entries) this.handleResizeEvent(entry); + }); + this.resizeObserver.observe(element); + } + removeResizeObserver(element) { + if (this.resizeObserver) this.resizeObserver.unobserve(element); + } + handleIntersectionEvent(entry) { + let target = entry.target, + event = new CustomEvent("visible", { bubbles: !1, detail: entry }); + target.dispatchEvent(event); + } + createIntersectionObserver(element) { + if (!this.intersectionObserver) + this.intersectionObserver = new IntersectionObserver((entries) => { + for (let entry of entries) this.handleIntersectionEvent(entry); + }); + this.intersectionObserver.observe(element); + } + removeIntersectionObserver(element) { + if (this.intersectionObserver) this.intersectionObserver.unobserve(element); + } + createListener(event_name, element, bubbles) { + if (event_name == "resize") this.createResizeObserver(element); + else if (event_name == "visible") this.createIntersectionObserver(element); + if (bubbles) + if (this.global[event_name] === void 0) + (this.global[event_name] = { active: 1, callback: this.handler }), + this.root.addEventListener(event_name, this.handler); + else this.global[event_name].active++; + else { + let id = element.getAttribute("data-dioxus-id"); + if (!this.local[id]) this.local[id] = {}; + element.addEventListener(event_name, this.handler); + } + } + removeListener(element, event_name, bubbles) { + if (event_name == "resize") this.removeResizeObserver(element); + else if (event_name == "visible") this.removeIntersectionObserver(element); + else if (bubbles) this.removeBubblingListener(event_name); + else this.removeNonBubblingListener(element, event_name); + } + removeBubblingListener(event_name) { + if ( + (this.global[event_name].active--, this.global[event_name].active === 0) + ) + this.root.removeEventListener( + event_name, + this.global[event_name].callback + ), + delete this.global[event_name]; + } + removeNonBubblingListener(element, event_name) { + let id = element.getAttribute("data-dioxus-id"); + if ( + (delete this.local[id][event_name], + Object.keys(this.local[id]).length === 0) + ) + delete this.local[id]; + element.removeEventListener(event_name, this.handler); + } + removeAllNonBubblingListeners(element) { + let id = element.getAttribute("data-dioxus-id"); + delete this.local[id]; + } + getNode(id) { + return this.nodes[id]; + } + pushRoot(node) { + this.stack.push(node); + } + appendChildren(id, many) { + let root = this.nodes[id], + els = this.stack.splice(this.stack.length - many); + for (let k = 0; k < many; k++) root.appendChild(els[k]); + } + loadChild(ptr, len) { + let node = this.stack[this.stack.length - 1], + ptr_end = ptr + len; + for (; ptr < ptr_end; ptr++) { + let end = this.m.getUint8(ptr); + for (node = node.firstChild; end > 0; end--) node = node.nextSibling; + } + return node; + } + saveTemplate(nodes, tmpl_id) { + this.templates[tmpl_id] = nodes; + } + hydrate_node(hydrateNode, ids) { + let split = hydrateNode.getAttribute("data-node-hydration").split(","), + id = ids[parseInt(split[0])]; + if (((this.nodes[id] = hydrateNode), split.length > 1)) { + (hydrateNode.listening = split.length - 1), + hydrateNode.setAttribute("data-dioxus-id", id.toString()); + for (let j = 1; j < split.length; j++) { + let split2 = split[j].split(":"), + event_name = split2[0], + bubbles = split2[1] === "1"; + this.createListener(event_name, hydrateNode, bubbles); + } + } + } + hydrate(ids, underNodes) { + for (let i = 0; i < underNodes.length; i++) { + let under = underNodes[i]; + if (under instanceof HTMLElement) { + if (under.getAttribute("data-node-hydration")) + this.hydrate_node(under, ids); + let hydrateNodes = under.querySelectorAll("[data-node-hydration]"); + for (let i2 = 0; i2 < hydrateNodes.length; i2++) + this.hydrate_node(hydrateNodes[i2], ids); + } + let treeWalker = document.createTreeWalker( + under, + NodeFilter.SHOW_COMMENT + ), + nextSibling = under.nextSibling, + continueToNextNode = () => { + if (!treeWalker.nextNode()) return !1; + return treeWalker.currentNode !== nextSibling; + }; + while (treeWalker.currentNode) { + let currentNode = treeWalker.currentNode; + if (currentNode.nodeType === Node.COMMENT_NODE) { + let id = currentNode.textContent, + placeholderSplit = id.split("placeholder"); + if (placeholderSplit.length > 1) { + if ( + ((this.nodes[ids[parseInt(placeholderSplit[1])]] = currentNode), + !continueToNextNode()) + ) + break; + continue; + } + let textNodeSplit = id.split("node-id"); + if (textNodeSplit.length > 1) { + let next = currentNode.nextSibling; + currentNode.remove(); + let commentAfterText, textNode; + if (next.nodeType === Node.COMMENT_NODE) { + let newText = next.parentElement.insertBefore( + document.createTextNode(""), + next + ); + (commentAfterText = next), (textNode = newText); + } else (textNode = next), (commentAfterText = textNode.nextSibling); + (treeWalker.currentNode = commentAfterText), + (this.nodes[ids[parseInt(textNodeSplit[1])]] = textNode); + let exit = currentNode === under || !continueToNextNode(); + if ((commentAfterText.remove(), exit)) break; + continue; + } + } + if (!continueToNextNode()) break; + } + } + } + setAttributeInner(node, field, value, ns) { + setAttributeInner(node, field, value, ns); + } +} +export { BaseInterpreter }; diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 52452350f5..5e028a15eb 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -38,7 +38,7 @@ pub mod minimal_bindings { pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>); /// Roll up all the values from the node into a JS object that we can deserialize - pub fn collectFormValues(node: JsValue) -> JsValue; + pub fn retrieveFormValues(node: JsValue) -> JsValue; } #[wasm_bindgen(module = "/src/js/hydrate.js")] diff --git a/packages/logger/src/lib.rs b/packages/logger/src/lib.rs index df78c8b567..1e7318a7eb 100644 --- a/packages/logger/src/lib.rs +++ b/packages/logger/src/lib.rs @@ -4,6 +4,7 @@ use tracing::{ }; pub use tracing; +pub use tracing::{debug, error, info, trace, warn}; /// Attempt to initialize the subscriber if it doesn't already exist, with default settings. /// diff --git a/packages/subsecond/subsecond-cli-support/Cargo.toml b/packages/subsecond/subsecond-cli-support/Cargo.toml index ced660c6b2..3bcda1b276 100644 --- a/packages/subsecond/subsecond-cli-support/Cargo.toml +++ b/packages/subsecond/subsecond-cli-support/Cargo.toml @@ -18,3 +18,12 @@ subsecond-types = { workspace = true } libc = "0.2.155" target-lexicon = "0.13.2" tracing = { workspace = true } + + +walrus = { workspace = true, features = ["parallel"]} +wasmparser = { workspace = true } +id-arena = { workspace = true } +rayon = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +clap = { workspace = true, features = ["derive"] } + diff --git a/packages/subsecond/subsecond-cli-support/src/lib.rs b/packages/subsecond/subsecond-cli-support/src/lib.rs index 6aea897cbd..f9cea229f9 100644 --- a/packages/subsecond/subsecond-cli-support/src/lib.rs +++ b/packages/subsecond/subsecond-cli-support/src/lib.rs @@ -16,6 +16,7 @@ use tokio::process::Command; pub mod partial; pub mod resolve; +pub mod wasm; pub fn create_jump_table( original: &Path, diff --git a/packages/subsecond/subsecond-cli-support/src/resolve.rs b/packages/subsecond/subsecond-cli-support/src/resolve.rs index 29d12736cc..e6c830f4a5 100644 --- a/packages/subsecond/subsecond-cli-support/src/resolve.rs +++ b/packages/subsecond/subsecond-cli-support/src/resolve.rs @@ -59,13 +59,15 @@ pub fn resolve_undefined( match triple.binary_format { target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf, target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO, - target_lexicon::BinaryFormat::Coff => todo!(), - target_lexicon::BinaryFormat::Wasm => todo!(), - target_lexicon::BinaryFormat::Xcoff => todo!(), + target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff, + target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm, + target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff, _ => todo!(), }, match triple.architecture { target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64, + target_lexicon::Architecture::Wasm32 => object::Architecture::Wasm32, + target_lexicon::Architecture::X86_64 => object::Architecture::X86_64, _ => todo!(), }, match triple.endianness() { @@ -127,6 +129,10 @@ pub fn resolve_undefined( } }; + if triple.architecture == target_lexicon::Architecture::Wasm32 { + return Ok(vec![]); + } + // 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); diff --git a/packages/subsecond/subsecond-cli-support/src/wasm-incrs.txt b/packages/subsecond/subsecond-cli-support/src/wasm-incrs.txt new file mode 100644 index 0000000000..ff6fdc1281 --- /dev/null +++ b/packages/subsecond/subsecond-cli-support/src/wasm-incrs.txt @@ -0,0 +1,98 @@ +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.04c7qof3py2bbvqyn0husbqsk.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.04jzqzlmy49snl2ufgv6nvzkv.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.0bblm4f57alrol7hovgbano99.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.0jv9fwswgrk79lndb7g5q1bv7.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.0kp46a5q6jsjm4difnhofxeft.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.0nnwl69bavkugsvna2qjuu698.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.0ovdbqzq307gm6jmhosrmy6y7.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.0vv60wy3f4d34zaorcfmt994c.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.13k9f8ea59r38u0kvm0feomyg.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.17b5qextd6ec0mmummjt5774z.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.1ehlb8ft9dns20n00z922gswl.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.1zeom8pxwkceo9bxnvwpaoyqn.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.22s89vo8o7sg1wmheg9mt5rw1.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.264b3udf17ybua1co1qi19al7.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.28dey4oya5sacj70v260dhig1.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.28o0mji3q38m7l4kna87d4klp.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.2pra35wde0kjrxdvrhh3jqafj.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.2qia1r51efj0bpbgg8ouffc5h.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.2qxynvey40ozfvbc0gf56hmr5.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.2wno5y86b41ypi8arwsdjsfhm.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.3o4bcjtzjm55nto7upfnzddjl.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.4nizo1c8jhiqax2420b9lkud8.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.4taroik285owq3x193805ncjn.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.4wkvephc68zkdhmwkqlinc6vy.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.50e6o2oe2f6enhvoeztodvxmz.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.53kiebn6nygbwxxvo75kxmmcp.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.587nrngnmwl12urhnmwk9rq65.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.5atu7xij0q566uqarj5uv5mke.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.5dr7gfghrtktob8x4vz0rxvho.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.5ihcae34uisqdqd5runasui2q.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.5jwk5mktoi3w9ddfsmo1qehrn.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.5q52xqbwwv3zrsc0l48ejygeo.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.614ry4ij55wgl1hqbosd97kgy.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.61i4hlka7yiw21rl7lqvghlen.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7aqdxwzksm4m81gxmh3qa9i2e.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7bm2i1gf0jjkyejtbvcfhm7h8.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7dzm7l6hnw7cpcnzy8t668j3o.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7imbq6zvq4f4a6w2cnrbmq5ox.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7k1k7yikbr4nglzqk1de3x0e1.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7zejm5pxlvsb51afdpqq1zk0r.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.80dsxj5xy1fj0fo4kptbc4tef.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.84pcnjozk56l32fxeukl5suvs.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.88onliqphilf3j45v4ib5lqvh.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.8ggh6cioqnfhxhk5ulgogsh56.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.8oext0qzn0m9tqa683vvz8bpz.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.8re3s6mzgydjclssv1dxmeor5.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.8shm3bekmo89canojbqz7yh11.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.8vwq7ri0t6kvcdvj0pwhno8qp.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.8wiytld2hox9xhavvqa1le7rh.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.92sj2rnojqftm2ii288nx240k.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.956fqvp6kmnuy55n40tvj6tlw.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.97ak8pr8g0hyvsszjje6eop89.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.99pu9ndmexmrefi0madqwdoq6.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.9fyo2knrtt67xhsl31b33dv63.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.9lthronksr37k0qr69djusl09.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.9sgmc5nm35aw594hualrknaaj.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.a0cvn07uuvhsibq4rd0bffssu.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.af2t1dbn93y1hy3rppbfyt863.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.ancf93w51zwi93hz82v12e023.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.axpjqfado6hrs26rr6ociemm7.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.ayai3hk0jqohv37nhc95v2swn.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.b287zn4552hregoczz927j7yo.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.b6gyuz8auqj1pbq8a05ra6env.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.b9oksbqswskwht5gltw6saj1v.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.bhdvcdrpx9fgyouhk5buotwas.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.bin5y5p8j7fbdzjquokru9c0t.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.bk1tndpcamtp26ox6p2ny6vaa.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.bmw5ggjkyxlu7sm5yyeog4srq.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.brlwuj2jqo0t8v6u0grl0u6jl.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.brm8lc21w0boklyz1afons2pn.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.c27wbbunghcvcs2xiixlia6ns.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.c3l89vh4c7lg42faearop6do8.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.c9sw6bf91d2r862hlozdifx0g.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.clxa451mbgl5h6rb0ppxybv8h.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.cp5jn6lnklzjwuqc5ff0szwmt.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.ctdiztjmq5wn14013spl102h1.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.dd5mqkvsec92dvadj1knsmta4.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.dggg6k2c1jqc6q3yt05vwxxlh.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.dhqm6ivdxklqoctd7ug6em35x.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.dqusoqsbz5dh8135jj978rbvs.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.dyoqq5niucame0bbfvld5kza0.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e2otslx6j9pz8llq4df0idahc.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e4enh690s95etvwjaj4dbhmta.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e4n6ui54qc4t7twwwpblql3ub.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e5bfmqh6s3hz5k0qq993sx0i1.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e8z74euxf0it0peglaj4yeeyj.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e9b61x7h8oij753i5ks4dv0ei.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.e9zpvdqc23jyl2sdswn2nwwz7.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.eegipei39bt1diqr2aaruc6um.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.efi821qywhrvm15zt0ir1zqgm.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.ekz3ipsis39c6myfgbooiit2d.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.eunrdpr0upr736r4wwok7d76o.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.ewpex1okwoie5vs9he0c4j115.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.exb65mv70g4w98u1na8ha0pkd.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.f1atqy3l8r6w8qn9sac9vu6hs.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.f3kc7w1y7hrjosy0znutyyh00.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.f4a7325m3ssrxezepha8fuu1h.rcgu.o +/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/subsecond-dev/deps/subsecond_harness-facdf629bac85a3f.7lfockaem6j0yk5ronwwvnmr4.rcgu.o diff --git a/packages/subsecond/subsecond-cli-support/src/wasm.rs b/packages/subsecond/subsecond-cli-support/src/wasm.rs new file mode 100644 index 0000000000..9f8cbd7bb5 --- /dev/null +++ b/packages/subsecond/subsecond-cli-support/src/wasm.rs @@ -0,0 +1,456 @@ +//! +//! +//! +//! The process for expanding the base module involves: +//! 1. Creating spare space in the ifunc table for new entries +//! 2. Create an ifunc "mirror" for every real function that could be patched so the side module can call its functions +//! 3. Export necessary items (globals) from the host (and prevent them from getting gc-ed!) +//! 4. Adjusting ifunc indices in the patch module to initialize in the new space from the base module +//! 5. Load the patch module which runs its initializers into the table slots +//! 6. When detouring the function, we need to call the appropriate ifunc index with the correct type +//! +//! When building the base module: +//! 1. Make the ifunc table growable +//! 2. Create a call_indirect shim for every function type +//! 3. Make sure to register all functions and globals with the ifunc table so they don't get gc-ed +//! +//! When building the patch module: +//! 1. Build the jump table with the correct indices (based on func_idx and type_idx) +//! 2. Move the ifunc initializers to the correct location +//! +//! Notes: +//! - When the user reloads the page, the patch will be lost. Either we need to reapply the patch or +//! compile again (completely). +//! - We could overwrite the ifunc table on new patches or just grow it. Overwriting would make the +//! patching system stateless, but could lead to corruption issues if old funcs and mixed with new funcs. +//! - We could store the ifunc table length in a global and use a expression table initializer rather than hardcoding +//! the indices in the patch module. That's a very interesting idea. Will try and see if we can get it working. +//! - Actually *calling* the ifunc is a bit tricky. We need to "import" a function that matches the right +//! signature and then call it. +//! - If the function was already indirect (say a vtable/trait object) then that entry should already +//! exist in the ifunc table. Just at a different index (and maybe a different typeidx?) +//! - Calling it will involve generating an extern_c function for that type and calling it. During the +//! base module assembly process, we need to satisfy these imports. +//! - The patch module will need to call functions from the host and those will either need to be: +//! 1) in the ifunc table already or 2) generated for the patch module +//! - Thus I think we need to generate an indirect call for every function since the patch modules might want to call them (but can't). +//! We could use imports/exports to solve that too which might actually be easier (direct calls, isolated spaces, no need to worry about ifuncs). +//! - wasm_bindgen might be very difficult to work with. ugh. either it doesn't matter on the patch module or we need to run it on the patch. +//! in theory we should be running it on the patch but adding new bindgen at runtime is gross / might not even be practical. +//! - do we even need to modify the ifunc table? we can just pump the exported functions out into an object and have the patch module call them. +//! - wait yeah this whole thing is much much easier than I thought. we just need the name of the symbol / function index and we can call it directly. +//! +//! new approach: +//! - post-process the post bindgen module and export its functions + +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + ops::Range, + path::PathBuf, +}; +use wasmparser::{ + BinaryReader, Linking, LinkingSectionReader, Payload, RelocSectionReader, RelocationEntry, + SymbolInfo, +}; + +use anyhow::{Context, Result}; +use tokio::process::Command; +use walrus::{ + ir::{dfs_in_order, Visitor}, + FunctionId, FunctionKind, Module, +}; + +#[test] +fn test_ensure_matching() { + ensure_matching().unwrap(); +} + +fn ensure_matching() -> Result<()> { + let patch = include_bytes!("../../data/wasm-1/patch.wasm"); + let post_bind = include_bytes!("../../data/wasm-1/post-bindgen.wasm"); + + let patch_module = walrus::Module::from_buffer(patch).unwrap(); + let post_bindgen_module = walrus::Module::from_buffer(post_bind).unwrap(); + + for import in patch_module.imports.iter() { + println!("Import: {}", import.name); + } + + Ok(()) +} + +pub fn get_ifunc_table_length(bytes: &[u8]) -> usize { + let module = walrus::Module::from_buffer(bytes).unwrap(); + module + .tables + .iter() + .map(|table| table.elem_segments.iter()) + .flatten() + .map(|segment| match &module.elements.get(*segment).items { + walrus::ElementItems::Functions(ids) => ids.len(), + walrus::ElementItems::Expressions(ref_type, const_exprs) => const_exprs.len(), + }) + // .map(|table| table.elem_segments.len()) + .max() + .unwrap_or(1) +} + +/// Prepares the base module before running wasm-bindgen. +/// +/// This tries to work around how wasm-bindgen works by intelligently promoting non-wasm-bindgen functions +/// to the export table. +pub fn prepare_base_module(bytes: &[u8]) -> Result> { + let mut pre_bindgen = walrus::Module::from_buffer(bytes)?; + + let bindgen_funcs = collect_all_wasm_bindgen_funcs(&pre_bindgen); + + let raw_data = parse_bytes_to_data_segment(bytes)?; + + for func in bindgen_funcs.iter() { + let name = pre_bindgen.funcs.get(*func).name.as_ref().unwrap(); + // tracing::warn!("Wasm-bindgen function: {}", name); + } + + let funcs_to_export = pre_bindgen + .funcs + .iter() + .filter(|func| !bindgen_funcs.contains(&func.id())) + .filter(|func| matches!(func.kind, FunctionKind::Local(_))) + .map(|func| func.id()) + .collect::>(); + + let mut already_exported = pre_bindgen + .exports + .iter() + .map(|exp| exp.name.clone()) + .collect::>(); + + // tracing::info!("Already exported: {:#?}", already_exported); + + for import in pre_bindgen.imports.iter() { + tracing::error!("Import: {}", import.name); + // let name = import.name + // if name.contains("_ZN59_") { + // // if name.contains("dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt") { + // tracing::error!("found?: {}", name); + // } + } + for func in pre_bindgen.funcs.iter() { + let name = func.name.as_ref().unwrap(); + tracing::error!("Func [{}]: {}", func.id().index(), name); + // if name.contains("_ZN59_") { + // 2025-03-23T09:22:07.067150Z  INFO subsecond_cli_support::wasm: Func [28878]: _ZN59_$LT$dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt17haa1f6a0961c11078E + // if name.contains("dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt") { + // tracing::error!("found?: {}", name); + // } + } + + for func in funcs_to_export { + let func = pre_bindgen.funcs.get(func); + let name = func.name.as_ref().unwrap(); + // if name.contains("a1f6a0961c1107") { + // tracing::error!("Skipping function: {}", name); + // } + + if !already_exported.contains(name) { + // tracing::info!("Exporting function: {}", name); + pre_bindgen.exports.add(&name, func.id()); + already_exported.insert(name.clone()); + } + } + + Ok(pre_bindgen.emit_wasm()) +} + +/// Collect all the wasm-bindgen functions in the module. We are going to make *everything* exported +/// but we don't want to make *these* exported. +fn collect_all_wasm_bindgen_funcs(module: &Module) -> HashSet { + const PREFIX: &str = "__wbindgen_describe_"; + + let mut acc = AccAllDescribes::default(); + for func in module.funcs.iter() { + let name = func.name.as_ref().unwrap(); + + // Only deal with the __wbindgen_describe_ functions + if !(name.starts_with(PREFIX) + || name.contains("wasm_bindgen..describe..WasmDescribe") + || name.contains("wasm_bindgen..closure..WasmClosure$GT$8describe") + || name.contains("wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe") + || name.contains("__wbindgen_describe_closure") + || name.contains("__wbindgen_externref_xform")) + { + continue; + } + + // They call other functions, so we need to find those too and make sure not to mark them as exported + if let FunctionKind::Local(func) = &module.funcs.get(func.id()).kind { + dfs_in_order(&mut acc, &func, func.entry_block()); + } + + acc.funcs.insert(func.id()); + } + + /// The __wbindgen_describe_ functions also reference funcs like _ZN86_$LT$dioxus_web..document..JSOwner$u20$as$u20$wasm_bindgen..describe..WasmDescribe$GT$8describe17ha9b39368d518c1f9E + /// These can be found by walking the instructions. + #[derive(Default)] + struct AccAllDescribes { + funcs: HashSet, + } + impl<'a> Visitor<'a> for AccAllDescribes { + fn visit_function_id(&mut self, function: &walrus::FunctionId) { + self.funcs.insert(*function); + } + } + + tracing::info!("Found {} wasm-bindgen functions", acc.funcs.len()); + + acc.funcs +} + +#[test] +fn test_prepare_patch_module() { + // --import-undefined + // --import-memory + // --unresolved-symbols=ignore-all + // --allow-undefined + // --relocatable Create relocatable object file + // --table-base= Table offset at which to place address taken functions (Defaults to 1) + // + // seems like we can just use these - import undefined and adjusted table base - to do this all within the linker + // just requires massaging the base module a bit + // do we need to run wasm-bindgen on this?? + prepare_patch_module(include_bytes!("../../data/wasm-1/patch.wasm")); +} + +fn prepare_patch_module(bytes: &[u8]) -> Result<()> { + let mut patch = walrus::Module::from_buffer(bytes)?; + + for func in patch.funcs.iter() { + let name = func.name.as_ref().unwrap(); + // if name.contains("describe") { + println!( + "Function [{}]: {}", + matches!(func.kind, FunctionKind::Local(_)), + name + ); + // } + // println!("Function: {}", name); + } + + Ok(()) +} + +async fn link_incrementals() { + let incrs = include_str!("./wasm-incrs.txt") + .lines() + .filter(|line| line.ends_with(".rcgu.o")) + .collect::>(); + + println!("{:?}", incrs); + + let res = Command::new(wasm_ld().await) + .args(incrs) + .arg("--growable-table") + .arg("--export") + .arg("main") + .arg("--export=__heap_base") + .arg("--export=__data_end") + .arg("-z") + .arg("stack-size=1048576") + .arg("--stack-first") + .arg("--allow-undefined") + .arg("--no-demangle") + .arg("--no-entry") + // .arg("--no-gc-sections") + .arg("-o") + .arg(wasm_data_folder().join("patch.wasm")) + .output() + .await + .unwrap(); + + let err = String::from_utf8(res.stderr).unwrap(); + let out = String::from_utf8(res.stdout).unwrap(); + println!("{}", err); +} + +async fn wasm_ld() -> PathBuf { + sysroot() + .await + .join("lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld") +} + +async fn sysroot() -> PathBuf { + let res = Command::new("rustc") + .arg("--print") + .arg("sysroot") + .output() + .await + .unwrap(); + + let path = String::from_utf8(res.stdout).unwrap(); + PathBuf::from(path.trim()) +} + +fn wasm_data_folder() -> PathBuf { + subsecond_folder().join("data").join("wasm") +} + +fn static_folder() -> PathBuf { + subsecond_folder().join("subsecond-harness").join("static") +} + +/// Folder representing dioxus/packages/subsecond +fn subsecond_folder() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../") + .canonicalize() + .unwrap() +} + +/// The incoming module is expecting to initialize its functions at address 1. +/// +/// We need to move it to match the base module's ifunc table. +pub fn move_func_initiailizers(bytes: &[u8]) -> Result> { + let mut module = walrus::Module::from_buffer(bytes)?; + + let table = module.tables.iter_mut().next().unwrap(); + table.initial = 1549; + let segments = table.elem_segments.clone(); + + for seg in segments { + match &mut module.elements.get_mut(seg).kind { + walrus::ElementKind::Passive => todo!(), + walrus::ElementKind::Declared => todo!(), + walrus::ElementKind::Active { table, offset } => { + tracing::info!("original offset {:?}", offset); + match offset { + walrus::ConstExpr::Value(value) => { + *value = walrus::ir::Value::I32(1549 + 1); + } + walrus::ConstExpr::Global(id) => {} + walrus::ConstExpr::RefNull(ref_type) => {} + walrus::ConstExpr::RefFunc(id) => {} + } + } + } + } + + Ok(module.emit_wasm()) +} + +struct RawDataSection<'a> { + data_range: Range, + symbols: Vec>, + data_symbols: BTreeMap, +} + +#[derive(Debug)] +struct DataSymbol { + index: usize, + range: Range, + segment_offset: usize, + symbol_size: usize, + which_data_segment: usize, +} + +/// Manually parse the data section from a wasm module +/// +/// We need to do this for data symbols because walrus doesn't provide the right range and offset +/// information for data segments. Fortunately, it provides it for code sections, so we only need to +/// do a small amount extra of parsing here. +fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result { + let parser = wasmparser::Parser::new(0); + let mut parser = parser.parse_all(bytes); + let mut segments = vec![]; + let mut data_range = 0..0; + let mut symbols = vec![]; + + // Process the payloads in the raw wasm file so we can extract the specific sections we need + while let Some(Ok(payload)) = parser.next() { + match payload { + Payload::DataSection(section) => { + data_range = section.range(); + segments = section.into_iter().collect::, _>>()? + } + Payload::CustomSection(section) if section.name() == "linking" => { + let reader = BinaryReader::new(section.data(), 0); + let reader = LinkingSectionReader::new(reader)?; + for subsection in reader.subsections() { + if let Linking::SymbolTable(map) = subsection? { + symbols = map.into_iter().collect::, _>>()?; + } + } + } + _ => {} + } + } + + // Accumulate the data symbols into a btreemap for later use + let mut data_symbols = BTreeMap::new(); + for (index, symbol) in symbols.iter().enumerate() { + match symbol { + SymbolInfo::Func { flags, index, name } => { + if let Some(name) = name { + tracing::info!("Func [{index}]: {}", name); + } + } + SymbolInfo::Data { + flags, + name, + symbol, + } => { + tracing::info!("Data: {}", name); + } + SymbolInfo::Global { flags, index, name } => {} + SymbolInfo::Section { flags, section } => {} + SymbolInfo::Event { flags, index, name } => {} + SymbolInfo::Table { flags, index, name } => {} + } + + let SymbolInfo::Data { + symbol: Some(symbol), + .. + } = symbol + else { + continue; + }; + + if symbol.size == 0 { + continue; + } + + let data_segment = segments + .get(symbol.index as usize) + .context("Failed to find data segment")?; + let offset: usize = + data_segment.range.end - data_segment.data.len() + (symbol.offset as usize); + let range = offset..(offset + symbol.size as usize); + + data_symbols.insert( + index, + DataSymbol { + index, + range, + segment_offset: symbol.offset as usize, + symbol_size: symbol.size as usize, + which_data_segment: symbol.index as usize, + }, + ); + } + + Ok(RawDataSection { + data_range, + symbols, + data_symbols, + }) +} + +#[tokio::test] +async fn test_link_incrementals() { + link_incrementals().await; +} + +#[test] +fn test_prepare_base_module() { + prepare_base_module(include_bytes!("../../data/wasm-1/pre-bindgen.wasm")); +} diff --git a/packages/subsecond/subsecond-cli/Cargo.toml b/packages/subsecond/subsecond-cli/Cargo.toml index 29fb08f62c..6062c6f704 100644 --- a/packages/subsecond/subsecond-cli/Cargo.toml +++ b/packages/subsecond/subsecond-cli/Cargo.toml @@ -39,3 +39,7 @@ crossterm = { workspace = true } subsecond-cli-support = { path = "../subsecond-cli-support" } target-lexicon = "0.13.2" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +axum = { workspace = true, features = ["ws"] } +axum-server = { workspace = true, features = ["tls-rustls"] } +axum-extra = { workspace = true, features = ["typed-header"] } +tower-http = { workspace = true, features = ["full"] } diff --git a/packages/subsecond/subsecond-cli/src/index.html b/packages/subsecond/subsecond-cli/src/index.html index effa1da35b..2c843bc84e 100644 --- a/packages/subsecond/subsecond-cli/src/index.html +++ b/packages/subsecond/subsecond-cli/src/index.html @@ -4,9 +4,69 @@ Subsecond + diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs index 9049318003..676bc02521 100644 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ b/packages/subsecond/subsecond-cli/src/main.rs @@ -10,7 +10,7 @@ use notify::{ use object::{write::Object, Architecture}; use serde::Deserialize; use std::{collections::HashMap, env, ffi::OsStr, path::PathBuf, process::Stdio, time::SystemTime}; -use subsecond_cli_support::create_jump_table; +use subsecond_cli_support::{create_jump_table, wasm::move_func_initiailizers}; use target_lexicon::{Environment, Triple}; use tokio::{ io::AsyncBufReadExt, @@ -67,9 +67,9 @@ async fn hotreload_loop() -> anyhow::Result<()> { // Perform the initial build let epoch = SystemTime::UNIX_EPOCH; let now = std::time::Instant::now(); - println!("Starting build for target {target:?}..."); + tracing::debug!("Starting build for target {target:?}..."); let result = initial_build(&target).await?; - println!( + tracing::debug!( "Initial build: {:?} -> {}", now.elapsed(), &result.output_location, @@ -89,9 +89,6 @@ async fn hotreload_loop() -> anyhow::Result<()> { // Wait for the websocket to come up let mut client = wait_for_ws(9393, &target).await?; - // don't log if the screen has been taken over - important for tui apps - let should_log = rust_log_enabled(); - // Watch the source folder for changes let mut watcher = FsWatcher::watch(src_folder)?; @@ -107,20 +104,19 @@ async fn hotreload_loop() -> anyhow::Result<()> { tracing::info!("Fast reloading... "); let started = Instant::now(); - let Ok(output_temp) = - fast_build(&result, &target, client.as_ref().map(|s| s.aslr_reference)).await - else { - continue; - }; + let output_temp = + match fast_build(&result, &target, client.as_ref().map(|s| s.aslr_reference)).await { + Ok(output_temp) => output_temp, + Err(e) => { + tracing::warn!("Fast build failed: {e}"); + continue; + } + }; // Assemble the jump table of redirected addresses // todo: keep track of this and merge it over time - let jump_table = create_jump_table( - fat_exe.as_std_path(), - output_temp.as_std_path(), - &Triple::host(), - ) - .unwrap(); + let jump_table = + create_jump_table(fat_exe.as_std_path(), output_temp.as_std_path(), &target).unwrap(); if let Some(client) = client.as_mut() { client @@ -131,6 +127,13 @@ async fn hotreload_loop() -> anyhow::Result<()> { .await?; } + if target.architecture == target_lexicon::Architecture::Wasm32 { + let _ = std::fs::copy( + output_temp.as_std_path(), + static_folder().join(output_temp.file_name().unwrap()), + ); + } + tracing::info!("Patching complete in {}ms", started.elapsed().as_millis()) } @@ -151,6 +154,8 @@ fn launch_app(fat_exe: &Utf8PathBuf, target: &Triple) -> Result Command::new(fat_exe).kill_on_drop(true).spawn()?, @@ -242,6 +247,10 @@ async fn link(action: String) -> anyhow::Result<()> { // Actually link the object file. todo: figure out which linker we should be using "link" => {} + // // Run the wasm-link process + // // This involves finding all the non-describe functions and calling --export on them + // "wasm-link" => {} + // Write a dummy object file to the output file to satisfy rust when it tries to strip the symbols "patch" => { let out = args.iter().position(|arg| arg == "-o").unwrap(); @@ -314,8 +323,9 @@ async fn initial_build(target: &Triple) -> anyhow::Result { build.arg("-Clink-arg=--no-gc-sections"); build.arg("-Clink-arg=--growable-table"); build.arg("-Clink-arg=--whole-archive"); - // build.arg("-Clink-arg=--export-all"); - // build.arg("-Clink-arg=--export-dynamic"); + build.arg("-Clink-arg=--export-table"); + build.arg("-Clink-arg=--export-memory"); + build.arg("-Clink-arg=--emit-relocs"); } _ => {} } @@ -334,7 +344,20 @@ async fn initial_build(target: &Triple) -> anyhow::Result { let out = run_cargo_output(build, rust_log_enabled()).await?; if target.architecture == target_lexicon::Architecture::Wasm32 { - std::fs::remove_dir_all(static_folder()).unwrap(); + _ = std::fs::remove_dir_all(static_folder()); + + let test_data_folder = wasm_data_folder(); + let _ = std::fs::create_dir_all(&test_data_folder); + let _ = std::fs::copy( + out.output_location.as_std_path(), + test_data_folder.join("pre-bindgen.wasm"), + ); + + let unprocessed = std::fs::read(out.output_location.as_std_path())?; + let all_exported_bytes = + subsecond_cli_support::wasm::prepare_base_module(&unprocessed).unwrap(); + let processed = test_data_folder.join("processed.wasm"); + std::fs::write(&processed, all_exported_bytes)?; let bind = Command::new("wasm-bindgen") .arg("--target") @@ -345,7 +368,9 @@ async fn initial_build(target: &Triple) -> anyhow::Result { .arg("--out-name") .arg("main") .arg("--no-demangle") - .arg(out.output_location.as_std_path()) + .arg("--keep-lld-exports") + .arg("--keep-debug") + .arg(&processed) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(true) @@ -353,6 +378,16 @@ async fn initial_build(target: &Triple) -> anyhow::Result { .output() .await?; + let err = String::from_utf8(bind.stderr).unwrap(); + if !err.is_empty() { + tracing::error!("err: {err}"); + } + + let _ = std::fs::copy( + static_folder().join("main_bg.wasm"), + test_data_folder.join("post-bindgen.wasm"), + ); + let index = include_str!("./index.html"); std::fs::write(static_folder().join("index.html"), index).unwrap(); } @@ -360,6 +395,10 @@ async fn initial_build(target: &Triple) -> anyhow::Result { Ok(out) } +fn wasm_data_folder() -> PathBuf { + subsecond_folder().join("data").join("wasm") +} + fn static_folder() -> PathBuf { subsecond_folder().join("subsecond-harness").join("static") } @@ -401,8 +440,21 @@ async fn fast_build( .map(|arg| PathBuf::from(arg)) .collect::>(); - tracing::info!("object_files: {object_files:#?}"); + // copy incrementals to the data folder + if target.architecture == target_lexicon::Architecture::Wasm32 { + let test_data_folder = wasm_data_folder().join("incrementals"); + let _ = std::fs::create_dir_all(&test_data_folder); + for object in object_files.iter() { + let dest = test_data_folder.join(object.file_name().unwrap()); + std::fs::copy(object, dest)?; + } + } + + // tracing::info!("object files: {object_files:#?}"); + // on wasm we'll need to add in some symbols that resolve to the ifunc table + // unfortunately we can't quite call functions from main directly so we need to go through the ifunc system + // I *think* we can just import them let resolved = subsecond_cli_support::resolve::resolve_undefined( &original.output_location.as_std_path(), &object_files, @@ -412,12 +464,20 @@ async fn fast_build( .unwrap(); let syms = subsecond_folder().join("data").join("syms.o"); std::fs::write(&syms, resolved).unwrap(); - object_files.push(syms); + if target.architecture != target_lexicon::Architecture::Wasm32 { + object_files.push(syms); + } - let output_location = original.output_location.with_file_name(format!( - "patch-{}", - SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() - )); + let output_location = original + .output_location + .with_file_name(format!( + "patch-{}", + SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() + )) + .with_extension(match target.architecture { + target_lexicon::Architecture::Wasm32 => "wasm", + _ => "", + }); let res = match target.architecture { // usually just ld64 - uses your `cc` @@ -438,14 +498,27 @@ async fn fast_build( .await? } target_lexicon::Architecture::Wasm32 => { - let ld = wasm_ld().await?; // --import-memory Import the module's memory from the default module of "env" with the name "memory". // --import-table Import function table from the environment - Command::new(ld) + let base = subsecond_cli_support::wasm::get_ifunc_table_length( + &std::fs::read(&original.output_location).unwrap(), + ); + tracing::info!("base: {base}"); + Command::new(wasm_ld().await.unwrap()) .args(object_files) - .arg("-Wl,--import-memory") - .arg("-Wl,--import-table") - .arg("-Wl,--growable-table") + .arg("--import-memory") + .arg("--import-table") + .arg("--growable-table") + .arg("--export") + .arg("main") + .arg("--export=__heap_base") + .arg("--export=__data_end") + .arg("-z") + .arg("stack-size=1048576") + .arg("--stack-first") + .arg("--allow-undefined") + .arg("--no-demangle") + .arg("--no-entry") .arg("-o") .arg(&output_location) .stdout(Stdio::piped()) @@ -461,6 +534,12 @@ async fn fast_build( tracing::error!("errs: {errs}"); } + if target.architecture == target_lexicon::Architecture::Wasm32 { + let out_bytes = std::fs::read(&output_location).unwrap(); + let res_ = move_func_initiailizers(&out_bytes).unwrap(); + std::fs::write(&output_location, res_).unwrap(); + } + Ok(output_location) } @@ -469,6 +548,7 @@ struct CargoOutputResult { output_location: Utf8PathBuf, direct_rustc: Vec, link_args: Vec, + table_size: usize, } async fn run_cargo_output( @@ -500,7 +580,7 @@ async fn run_cargo_output( Some(Ok(message)) => message, None => break, other => { - // println!("other: {other:?}"); + tracing::trace!("other: {other:?}"); break; } }; @@ -513,9 +593,7 @@ async fn run_cargo_output( } Message::CompilerMessage(compiler_message) => { if let Some(rendered) = &compiler_message.message.rendered { - // if should_render { - // println!("rendered: {rendered}"); - // } + tracing::trace!("rendered: {rendered}"); } } Message::BuildScriptExecuted(_build_script) => {} @@ -553,9 +631,7 @@ async fn run_cargo_output( } } - // if should_render { - // println!("text: {word}") - // } + tracing::trace!("text: {word}") } _ => {} } @@ -569,18 +645,20 @@ async fn run_cargo_output( output_location, link_args, direct_rustc, + table_size: 0, }) } async fn wasm_ld() -> anyhow::Result { - let root = Command::new("rustc") - .arg("--print") - .arg("--sysroot") - .output() - .await?; - let root = String::from_utf8(root.stdout)?; - let root = PathBuf::from(root.trim()); - Ok(root.join("lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld")) + Ok("/Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld".into()) + // let root = Command::new("rustc") + // .arg("--print") + // .arg("--sysroot") + // .output() + // .await?; + // let root = String::from_utf8(root.stdout)?; + // let root = PathBuf::from(root.trim()); + // Ok(root.join("lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld")) } fn workspace_dir() -> PathBuf { diff --git a/packages/subsecond/subsecond-cli/src/patch.js b/packages/subsecond/subsecond-cli/src/patch.js new file mode 100644 index 0000000000..1844f6b7ff --- /dev/null +++ b/packages/subsecond/subsecond-cli/src/patch.js @@ -0,0 +1,41 @@ +/** + * Patches a WebAssembly module by replacing its exports with those from another module. + * + * @param {WebAssembly.Exports} base - The base WebAssembly exports object to be patched. + * @param {WebAssembly.Exports} patch - The WebAssembly exports object containing the patch functions. + * @throws {TypeError} If the export names cannot be parsed as integers. + */ +function patchWasm(base, patch) { + const patchExports = Object.entries(patch); + + // extract the export names from the patch table + const patchVec = new Uint32Array(patchExports.length * 2); + + // iterate through the patch exports and get the key and value + let idx = 0; + for (const [key, value] of patchExports) { + patchVec[idx] = parseInt(base[key].name); + patchVec[idx + 1] = parseInt(value.name); + idx += 2; + } + + // call the patch function + base["__subsecondPatch"](patchVec); +} + +/** + * Loads a WebAssembly module from a given URL, instantiates it, and applies a patch + * to the provided base object using the module's exported functions. + * + * @param {WebAssembly.Exports} base - The base object to be patched with the WebAssembly module's exports. + * @param {string} url - The URL of the WebAssembly module to fetch and instantiate. + * @returns {void} This function does not return a value. + */ +function loadAndPatch(base, url) { + fetch(url) + .then((response) => response.arrayBuffer()) + .then((bytes) => WebAssembly.instantiate(bytes)) + .then((result) => { + patchWasm(base, result.instance.exports); + }); +} diff --git a/packages/subsecond/subsecond-cli/src/patch.ts b/packages/subsecond/subsecond-cli/src/patch.ts new file mode 100644 index 0000000000..f755ddf473 --- /dev/null +++ b/packages/subsecond/subsecond-cli/src/patch.ts @@ -0,0 +1,32 @@ +function loadAndPatch( + base: WebAssembly.Exports, + url: string, +) { + fetch(url) + .then((response) => response.arrayBuffer()) + .then((bytes) => WebAssembly.instantiate(bytes)) + .then((result) => { + patchWasm(base, result.instance.exports); + }); +} + +function patchWasm( + base: WebAssembly.Exports, + patch: WebAssembly.Exports +) { + const patchExports = Object.entries(patch); + + // extract the export names from the patch table + const patchVec = new Uint32Array(patchExports.length * 2); + + // iterate through the patch exports and get the key and value + let idx = 0; + for (const [key, value] of patchExports) { + patchVec[idx] = parseInt((base[key] as Function).name); + patchVec[idx + 1] = parseInt((value as Function).name); + idx += 2; + } + + // call the patch function + (base["__subsecondPatch"] as Function)(patchVec); +} diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs index 4e9bfb5928..c8095b553d 100644 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs @@ -5,13 +5,40 @@ pub fn launch() { } fn app() -> Element { - let count = 123456789; + let count = 123; + + dioxus::logger::info!("Rendering component!"); + + // function pointers correlate directly to exports.table.get(pointer!) + // + // object.keys(patch_exports) -> gives us names of functions + // + // we end up making a map of original ptr to... what?? + // + // if we load the new module into the same table then its pointers should be valid too? + // + // the jump table can continue to be a map of ptr -> ptr + // + // but we somehow need to get the exports as pointers too + // + // the exported functions return a "pointer object" whose `name` field *is the pointer* (but as a string) + // + // so in theory we.... + // object.values(patch.exports) => [name, ptr] + // source.exports[name].name => ptr + // + // call patchJumpTable({ptr: ptr}) + + dioxus::logger::info!("fn ptr of app is {:?}", app as *const fn() -> Element); + dioxus::logger::info!("fn ptr of child is {:?}", Child as *const fn() -> Element); + dioxus::logger::info!("fn ptr of child2 is {:?}", Child2 as *const fn() -> Element); + dioxus::logger::info!("fn ptr of child3 is {:?}", Child3 as *const fn() -> Element); rsx! { "hi {count}" div { - for x in 0..2 { - Child { id: x, opt: "List entry" } + for x in 0..3 { + Child { id: x + 1, opt: "List entry" } } } } diff --git a/packages/subsecond/subsecond/Cargo.toml b/packages/subsecond/subsecond/Cargo.toml index ae754c039a..824e90bced 100644 --- a/packages/subsecond/subsecond/Cargo.toml +++ b/packages/subsecond/subsecond/Cargo.toml @@ -8,6 +8,9 @@ serde = { version = "1.0.203", features = ["derive"] } subsecond-macro = { workspace = true } subsecond-types = { workspace = true } +wasm-bindgen = { workspace = true } +js-sys = { workspace = true} + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libloading = "0.8.6" libc = "0.2.170" diff --git a/packages/subsecond/subsecond/src/wasm.rs b/packages/subsecond/subsecond/src/wasm.rs index e69de29bb2..d4268cae36 100644 --- a/packages/subsecond/subsecond/src/wasm.rs +++ b/packages/subsecond/subsecond/src/wasm.rs @@ -0,0 +1,21 @@ +use super::*; +use js_sys::Uint32Array; +use subsecond_types::AddressMap; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn __patch_wasm(pointers: Uint32Array) { + let mut table = JumpTable { + aslr_reference: 0, + lib: PathBuf::from("patch.wasm"), + map: AddressMap::default(), + new_base_address: 0, + old_base_address: 0, + }; + + for x in 0..(pointers.length() / 2) { + let left = pointers.get_index(x); + let right = pointers.get_index(x + 1); + table.map.insert(left as u64, right as u64); + } +} diff --git a/packages/web/src/document.rs b/packages/web/src/document.rs index b97402e7e5..d7e9d2c5c4 100644 --- a/packages/web/src/document.rs +++ b/packages/web/src/document.rs @@ -31,6 +31,16 @@ impl JSOwner { } } } +#[wasm_bindgen::prelude::wasm_bindgen(module = "/src/js/eval.js")] +extern "C" { + pub type WeakDioxusChannel; + + #[wasm_bindgen(method, js_name = "rustSend")] + pub fn rust_send(this: &WeakDioxusChannel, value: wasm_bindgen::JsValue); + + #[wasm_bindgen(method, js_name = "rustRecv")] + pub async fn rust_recv(this: &WeakDioxusChannel) -> wasm_bindgen::JsValue; +} #[wasm_bindgen::prelude::wasm_bindgen(module = "/src/js/eval.js")] extern "C" { @@ -54,13 +64,6 @@ extern "C" { #[wasm_bindgen(method)] pub fn weak(this: &WebDioxusChannel) -> WeakDioxusChannel; - pub type WeakDioxusChannel; - - #[wasm_bindgen(method, js_name = "rustSend")] - pub fn rust_send(this: &WeakDioxusChannel, value: wasm_bindgen::JsValue); - - #[wasm_bindgen(method, js_name = "rustRecv")] - pub async fn rust_recv(this: &WeakDioxusChannel) -> wasm_bindgen::JsValue; } /// Provides the Document through [`ScopeId::provide_context`]. diff --git a/packages/web/src/js/eval.js b/packages/web/src/js/eval.js index 111158c3ea..7da39ed47c 100644 --- a/packages/web/src/js/eval.js +++ b/packages/web/src/js/eval.js @@ -1 +1 @@ -class Channel{pending;waiting;constructor(){this.pending=[],this.waiting=[]}send(data){if(this.waiting.length>0){this.waiting.shift()(data);return}this.pending.push(data)}async recv(){return new Promise((resolve,_reject)=>{if(this.pending.length>0){resolve(this.pending.shift());return}this.waiting.push(resolve)})}}class WeakDioxusChannel{inner;constructor(channel){this.inner=new WeakRef(channel)}rustSend(data){let channel=this.inner.deref();if(channel)channel.rustSend(data)}async rustRecv(){let channel=this.inner.deref();if(channel)return await channel.rustRecv()}}class DioxusChannel{weak(){return new WeakDioxusChannel(this)}}class WebDioxusChannel extends DioxusChannel{js_to_rust;rust_to_js;owner;constructor(owner){super();this.owner=owner,this.js_to_rust=new Channel,this.rust_to_js=new Channel}weak(){return new WeakDioxusChannel(this)}async recv(){return await this.rust_to_js.recv()}send(data){this.js_to_rust.send(data)}rustSend(data){this.rust_to_js.send(data)}async rustRecv(){return await this.js_to_rust.recv()}}export{WebDioxusChannel}; +class Channel{pending;waiting;constructor(){this.pending=[],this.waiting=[]}send(data){if(this.waiting.length>0){this.waiting.shift()(data);return}this.pending.push(data)}async recv(){return new Promise((resolve,_reject)=>{if(this.pending.length>0){resolve(this.pending.shift());return}this.waiting.push(resolve)})}}class WeakDioxusChannel{inner;constructor(channel){this.inner=new WeakRef(channel)}rustSend(data){let channel=this.inner.deref();if(channel)channel.rustSend(data)}async rustRecv(){let channel=this.inner.deref();if(channel)return await channel.rustRecv()}}class DioxusChannel{weak(){return new WeakDioxusChannel(this)}}class WebDioxusChannel extends DioxusChannel{js_to_rust;rust_to_js;owner;constructor(owner){super();this.owner=owner,this.js_to_rust=new Channel,this.rust_to_js=new Channel}weak(){return new WeakDioxusChannel(this)}async recv(){return await this.rust_to_js.recv()}send(data){this.js_to_rust.send(data)}rustSend(data){this.rust_to_js.send(data)}async rustRecv(){return await this.js_to_rust.recv()}}export{WebDioxusChannel,WeakDioxusChannel}; diff --git a/packages/web/src/js/hash.txt b/packages/web/src/js/hash.txt index 5002e6c9aa..ff6c0119db 100644 --- a/packages/web/src/js/hash.txt +++ b/packages/web/src/js/hash.txt @@ -1 +1 @@ -[1614426347475783279] +[3447431072648601413] \ No newline at end of file diff --git a/packages/web/src/ts/eval.ts b/packages/web/src/ts/eval.ts index 51396b63a6..9fcf63a870 100644 --- a/packages/web/src/ts/eval.ts +++ b/packages/web/src/ts/eval.ts @@ -4,6 +4,7 @@ import { WeakDioxusChannel, } from "../../../document/src/ts/eval"; +export { WeakDioxusChannel }; export class WebDioxusChannel extends DioxusChannel { js_to_rust: Channel; rust_to_js: Channel; From 5297bbbbd7d7879f7af4d54c94af5e8acc7da660 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 23 Mar 2025 13:42:24 -0700 Subject: [PATCH 033/301] goly gee wasm hot patching works --- Cargo.lock | 1 + packages/subsecond/.vscode/settings.json | 1 + .../subsecond-cli-support/src/wasm.rs | 147 +++++++++++++----- .../subsecond/subsecond-cli/src/index.html | 29 +++- packages/subsecond/subsecond-cli/src/main.rs | 9 +- .../subsecond-harness/src/dioxus_demo.rs | 15 +- packages/subsecond/subsecond/Cargo.toml | 2 + packages/subsecond/subsecond/src/wasm.rs | 21 ++- 8 files changed, 168 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5dc7692318..966fbe0a29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12311,6 +12311,7 @@ dependencies = [ "serde", "subsecond-macro", "subsecond-types", + "tracing", "wasm-bindgen", ] diff --git a/packages/subsecond/.vscode/settings.json b/packages/subsecond/.vscode/settings.json index 7b76e82e49..b3c3f22e89 100644 --- a/packages/subsecond/.vscode/settings.json +++ b/packages/subsecond/.vscode/settings.json @@ -3,4 +3,5 @@ // // "wasm32-unknown-unknown", // // "aarch64-apple-darwin" // ], + "dioxus.formatOnSave": "disabled" } diff --git a/packages/subsecond/subsecond-cli-support/src/wasm.rs b/packages/subsecond/subsecond-cli-support/src/wasm.rs index 9f8cbd7bb5..707635f35a 100644 --- a/packages/subsecond/subsecond-cli-support/src/wasm.rs +++ b/packages/subsecond/subsecond-cli-support/src/wasm.rs @@ -104,61 +104,85 @@ pub fn prepare_base_module(bytes: &[u8]) -> Result> { let bindgen_funcs = collect_all_wasm_bindgen_funcs(&pre_bindgen); + // Due to monomorphizations, functions will get merged and multiple names will point to the same function. + // Walrus loses this information, so we need to manually parse the names table to get the indices + // and names of these functions. let raw_data = parse_bytes_to_data_segment(bytes)?; - for func in bindgen_funcs.iter() { - let name = pre_bindgen.funcs.get(*func).name.as_ref().unwrap(); - // tracing::warn!("Wasm-bindgen function: {}", name); - } + // name -> index + // we want to export *all* these functions + let all_funcs = raw_data + .symbols + .iter() + .flat_map(|sym| match sym { + SymbolInfo::Func { flags, index, name } => Some((name.unwrap(), *index)), + _ => None, + }) + .collect::>(); - let funcs_to_export = pre_bindgen + let index_to_func = pre_bindgen .funcs .iter() - .filter(|func| !bindgen_funcs.contains(&func.id())) - .filter(|func| matches!(func.kind, FunctionKind::Local(_))) - .map(|func| func.id()) - .collect::>(); + .enumerate() + .collect::>(); let mut already_exported = pre_bindgen .exports .iter() .map(|exp| exp.name.clone()) + .chain( + bindgen_funcs + .iter() + .map(|id| pre_bindgen.funcs.get(*id).name.as_ref().unwrap().clone()), + ) .collect::>(); - // tracing::info!("Already exported: {:#?}", already_exported); - - for import in pre_bindgen.imports.iter() { - tracing::error!("Import: {}", import.name); - // let name = import.name - // if name.contains("_ZN59_") { - // // if name.contains("dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt") { - // tracing::error!("found?: {}", name); - // } - } - for func in pre_bindgen.funcs.iter() { - let name = func.name.as_ref().unwrap(); - tracing::error!("Func [{}]: {}", func.id().index(), name); - // if name.contains("_ZN59_") { - // 2025-03-23T09:22:07.067150Z  INFO subsecond_cli_support::wasm: Func [28878]: _ZN59_$LT$dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt17haa1f6a0961c11078E - // if name.contains("dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt") { - // tracing::error!("found?: {}", name); - // } - } - - for func in funcs_to_export { - let func = pre_bindgen.funcs.get(func); - let name = func.name.as_ref().unwrap(); - // if name.contains("a1f6a0961c1107") { - // tracing::error!("Skipping function: {}", name); - // } + for (name, index) in all_funcs { + let func = index_to_func.get(&(index as usize)).unwrap(); + let FunctionKind::Local(local) = &func.kind else { + continue; + }; if !already_exported.contains(name) { - // tracing::info!("Exporting function: {}", name); pre_bindgen.exports.add(&name, func.id()); - already_exported.insert(name.clone()); + already_exported.insert(name.to_string()); } } + // let func = pre_bindgen.funcs.get(func); + // let name = func.name.as_ref().unwrap(); + // if name.contains("a1f6a0961c1107") { + // tracing::error!("Skipping function: {}", name); + // } + + // let funcs_to_export = pre_bindgen + // .funcs + // .iter() + // .filter(|func| !bindgen_funcs.contains(&func.id())) + // .filter(|func| matches!(func.kind, FunctionKind::Local(_))) + // .map(|func| func.id()) + // .collect::>(); + + // // tracing::info!("Already exported: {:#?}", already_exported); + + // for import in pre_bindgen.imports.iter() { + // tracing::error!("Import: {}", import.name); + // // let name = import.name + // // if name.contains("_ZN59_") { + // // // if name.contains("dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt") { + // // tracing::error!("found?: {}", name); + // // } + // } + // for func in pre_bindgen.funcs.iter() { + // let name = func.name.as_ref().unwrap(); + // tracing::error!("Func [{}]: {}", func.id().index(), name); + // // if name.contains("_ZN59_") { + // // 2025-03-23T09:22:07.067150Z  INFO subsecond_cli_support::wasm: Func [28878]: _ZN59_$LT$dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt17haa1f6a0961c11078E + // // if name.contains("dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt") { + // // tracing::error!("found?: {}", name); + // // } + // } + Ok(pre_bindgen.emit_wasm()) } @@ -325,7 +349,7 @@ pub fn move_func_initiailizers(bytes: &[u8]) -> Result> { tracing::info!("original offset {:?}", offset); match offset { walrus::ConstExpr::Value(value) => { - *value = walrus::ir::Value::I32(1549 + 1); + *value = walrus::ir::Value::I32(1700 + 1); } walrus::ConstExpr::Global(id) => {} walrus::ConstExpr::RefNull(ref_type) => {} @@ -335,6 +359,49 @@ pub fn move_func_initiailizers(bytes: &[u8]) -> Result> { } } + let bindgen_funcs = collect_all_wasm_bindgen_funcs(&module); + + // Due to monomorphizations, functions will get merged and multiple names will point to the same function. + // Walrus loses this information, so we need to manually parse the names table to get the indices + // and names of these functions. + let raw_data = parse_bytes_to_data_segment(bytes)?; + + // name -> index + // we want to export *all* these functions + let all_funcs = raw_data + .symbols + .iter() + .flat_map(|sym| match sym { + SymbolInfo::Func { flags, index, name } => Some((name.unwrap(), *index)), + _ => None, + }) + .collect::>(); + + let index_to_func = module.funcs.iter().enumerate().collect::>(); + + let mut already_exported = module + .exports + .iter() + .map(|exp| exp.name.clone()) + .chain( + bindgen_funcs + .iter() + .map(|id| module.funcs.get(*id).name.as_ref().unwrap().clone()), + ) + .collect::>(); + + for (name, index) in all_funcs { + let func = index_to_func.get(&(index as usize)).unwrap(); + let FunctionKind::Local(local) = &func.kind else { + continue; + }; + + if !already_exported.contains(name) { + module.exports.add(&name, func.id()); + already_exported.insert(name.to_string()); + } + } + Ok(module.emit_wasm()) } @@ -391,7 +458,7 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result { match symbol { SymbolInfo::Func { flags, index, name } => { if let Some(name) = name { - tracing::info!("Func [{index}]: {}", name); + // tracing::info!("Func [{index}]: {}", name); } } SymbolInfo::Data { @@ -399,7 +466,7 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result { name, symbol, } => { - tracing::info!("Data: {}", name); + // tracing::info!("Data: {}", name); } SymbolInfo::Global { flags, index, name } => {} SymbolInfo::Section { flags, section } => {} diff --git a/packages/subsecond/subsecond-cli/src/index.html b/packages/subsecond/subsecond-cli/src/index.html index 2c843bc84e..d130cb6195 100644 --- a/packages/subsecond/subsecond-cli/src/index.html +++ b/packages/subsecond/subsecond-cli/src/index.html @@ -13,6 +13,8 @@ * @throws {TypeError} If the export names cannot be parsed as integers. */ function patchWasm(base, patch) { + window.patch = patch; + patch = patch.instance.exports; const patchExports = Object.entries(patch); // extract the export names from the patch table @@ -21,8 +23,22 @@ // iterate through the patch exports and get the key and value let idx = 0; for (const [key, value] of patchExports) { - patchVec[idx] = parseInt(base[key].name); - patchVec[idx + 1] = parseInt(value.name); + if (base[key] === undefined) { + console.log("skipping", key); + continue; + } + + let old_ = base[key].name; + let new_ = value.name; + + if (old_ === undefined || new_ === undefined) { + console.log("skipping", key); + continue; + } + + console.log("patching", key, old_, new_); + patchVec[idx] = parseInt(old_); + patchVec[idx + 1] = parseInt(new_); idx += 2; } @@ -30,6 +46,7 @@ base["__patch_wasm"](patchVec); } + /** * Loads a WebAssembly module from a given URL, instantiates it, and applies a patch * to the provided base object using the module's exported functions. @@ -42,21 +59,23 @@ fetch(url) .then((response) => response.arrayBuffer()) .then((bytes) => { + base.__indirect_function_table.grow(2000); let imports = { env: { - __indirect_function_table: base.__wbindgen_export_7, + __indirect_function_table: base.__indirect_function_table, __stack_pointer: base.__stack_pointer, __tls_base: base.__tls_base, memory: base.memory, }, + // env: base.env }; - for (const key in Object.keys(base)) { + for (const key of Object.keys(base)) { imports.env[key] = base[key]; } return WebAssembly.instantiate(bytes, imports); }) .then((result) => { - patchWasm(base, result.instance.exports); + patchWasm(base, result); }); } diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs index 676bc02521..d77ebfc3c7 100644 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ b/packages/subsecond/subsecond-cli/src/main.rs @@ -511,10 +511,11 @@ async fn fast_build( .arg("--growable-table") .arg("--export") .arg("main") - .arg("--export=__heap_base") - .arg("--export=__data_end") - .arg("-z") - .arg("stack-size=1048576") + .arg("--export-all") + // .arg("--export=__heap_base") + // .arg("--export=__data_end") + // .arg("-z") + // .arg("stack-size=1048576") .arg("--stack-first") .arg("--allow-undefined") .arg("--no-demangle") diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs index c8095b553d..a11417b25e 100644 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs @@ -5,7 +5,7 @@ pub fn launch() { } fn app() -> Element { - let count = 123; + let count = 1234567; dioxus::logger::info!("Rendering component!"); @@ -29,16 +29,21 @@ fn app() -> Element { // // call patchJumpTable({ptr: ptr}) + let current = subsecond::current(app as fn() -> Element); + dioxus::logger::info!("fn ptr of app is {:?}", app as *const fn() -> Element); dioxus::logger::info!("fn ptr of child is {:?}", Child as *const fn() -> Element); dioxus::logger::info!("fn ptr of child2 is {:?}", Child2 as *const fn() -> Element); dioxus::logger::info!("fn ptr of child3 is {:?}", Child3 as *const fn() -> Element); rsx! { - "hi {count}" - div { - for x in 0..3 { - Child { id: x + 1, opt: "List entry" } + h1 { "Dioxus Hot-patch: {count}" } + div { style: "display: flex; flex-row: column; align-items: center; justify-content: center;", + img { src: "https://rustacean.net/assets/rustacean-flat-happy.png" } + div { + for x in 0..5 { + Child { id: x + 1, opt: "List entry" } + } } } } diff --git a/packages/subsecond/subsecond/Cargo.toml b/packages/subsecond/subsecond/Cargo.toml index 824e90bced..7d13c5e3be 100644 --- a/packages/subsecond/subsecond/Cargo.toml +++ b/packages/subsecond/subsecond/Cargo.toml @@ -8,6 +8,8 @@ serde = { version = "1.0.203", features = ["derive"] } subsecond-macro = { workspace = true } subsecond-types = { workspace = true } + +tracing = { workspace = true } wasm-bindgen = { workspace = true } js-sys = { workspace = true} diff --git a/packages/subsecond/subsecond/src/wasm.rs b/packages/subsecond/subsecond/src/wasm.rs index d4268cae36..8351384877 100644 --- a/packages/subsecond/subsecond/src/wasm.rs +++ b/packages/subsecond/subsecond/src/wasm.rs @@ -3,6 +3,8 @@ use js_sys::Uint32Array; use subsecond_types::AddressMap; use wasm_bindgen::prelude::*; +use super::*; + #[wasm_bindgen] pub fn __patch_wasm(pointers: Uint32Array) { let mut table = JumpTable { @@ -13,9 +15,22 @@ pub fn __patch_wasm(pointers: Uint32Array) { old_base_address: 0, }; - for x in 0..(pointers.length() / 2) { - let left = pointers.get_index(x); - let right = pointers.get_index(x + 1); + tracing::info!("Patching wasm with {:?}", pointers); + + let mut idx = 0; + for _ in 0..pointers.length() { + let left = pointers.get_index(idx); + let right = pointers.get_index(idx + 1); + tracing::info!("Adding pointer {:?} -> {:?}", left, right); table.map.insert(left as u64, right as u64); + idx += 2 + } + + unsafe { + APP_JUMP_TABLE = Some(table); + CHANGED = true; + HOTRELOAD_HANDLERS.clone().iter().for_each(|handler| { + handler(); + }); } } From 4eccd456f9fd802efd75256a4de0894eff57fcb9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 23 Mar 2025 22:59:54 -0700 Subject: [PATCH 034/301] clean up impl a bit --- Cargo.lock | 10 + packages/core/src/properties.rs | 4 +- packages/devtools/src/lib.rs | 7 +- .../subsecond-cli-support/src/resolve.rs | 3 +- .../subsecond/subsecond-cli/src/index.html | 132 ++-- packages/subsecond/subsecond-cli/src/main.rs | 26 +- packages/subsecond/subsecond-cli/src/patch.js | 41 -- packages/subsecond/subsecond-cli/src/patch.ts | 32 - .../subsecond-harness/src/dioxus_demo.rs | 29 +- .../subsecond-harness/src/ws_conn.rs | 2 +- packages/subsecond/subsecond/Cargo.toml | 1 + packages/subsecond/subsecond/src/android.rs | 104 --- packages/subsecond/subsecond/src/fn_impl.rs | 75 --- packages/subsecond/subsecond/src/lib.rs | 593 +++++++++++++----- packages/subsecond/subsecond/src/macho.rs | 1 - packages/subsecond/subsecond/src/unix.rs | 1 - packages/subsecond/subsecond/src/wasm.rs | 36 -- packages/subsecond/subsecond/src/windows.rs | 156 ----- 18 files changed, 554 insertions(+), 699 deletions(-) delete mode 100644 packages/subsecond/subsecond-cli/src/patch.js delete mode 100644 packages/subsecond/subsecond-cli/src/patch.ts delete mode 100644 packages/subsecond/subsecond/src/android.rs delete mode 100644 packages/subsecond/subsecond/src/fn_impl.rs delete mode 100644 packages/subsecond/subsecond/src/macho.rs delete mode 100644 packages/subsecond/subsecond/src/unix.rs delete mode 100644 packages/subsecond/subsecond/src/wasm.rs delete mode 100644 packages/subsecond/subsecond/src/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 966fbe0a29..c52385c722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3317,6 +3317,15 @@ dependencies = [ "matches", ] +[[package]] +name = "dbg_breakpoint" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb333c8dc699012c0694fe7c7eca7d625f2bceeeef44fd1de39008d6a6abf777" +dependencies = [ + "libc", +] + [[package]] name = "dbl" version = "0.3.2" @@ -12304,6 +12313,7 @@ dependencies = [ name = "subsecond" version = "0.6.3" dependencies = [ + "dbg_breakpoint", "js-sys", "libc", "libloading 0.8.6", diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 5fd08ba081..289788010b 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -188,7 +188,7 @@ where F: Fn(P) -> Element + Clone + 'static, { fn rebuild(&self, props: P) -> Element { - subsecond::current(self.clone()).call((props,)) + subsecond::HotFn::current(self.clone()).call((props,)) } } @@ -199,7 +199,7 @@ where F: Fn() -> Element + Clone + 'static, { fn rebuild(&self, props: ()) -> Element { - subsecond::current(self.clone()).call(props) + subsecond::HotFn::current(self.clone()).call(props) } } diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index cf5e1d44f2..ca491eb58c 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -30,9 +30,8 @@ impl Devtools { /// Assets need to be handled by the renderer. pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { dom.runtime().on_scope(ScopeId::ROOT, || { - // Update signals... + // 1. Update signals... let ctx = dioxus_signals::get_global_context(); - for template in &msg.templates { let value = template.template.clone(); let key = GlobalKey::File { @@ -50,6 +49,7 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { } } + // 2. Attempt to hotpatch if let Some(mut jump_table) = msg.jump_table.as_ref().cloned() { if cfg!(target_os = "android") { // // copy the jump table to the libs directory to satisfy the namespace requirements @@ -71,7 +71,8 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { println!("Patched jump table: {:#?}", jump_table); } - unsafe { subsecond::run_patch(jump_table) }; + unsafe { subsecond::apply_patch(jump_table) }; + dioxus_core::prelude::force_all_dirty(); } }); diff --git a/packages/subsecond/subsecond-cli-support/src/resolve.rs b/packages/subsecond/subsecond-cli-support/src/resolve.rs index e6c830f4a5..f8ddbef3c7 100644 --- a/packages/subsecond/subsecond-cli-support/src/resolve.rs +++ b/packages/subsecond/subsecond-cli-support/src/resolve.rs @@ -29,7 +29,7 @@ pub fn resolve_undefined( source: &Path, incrementals: &[PathBuf], triple: &Triple, - aslr_reference: Option, + aslr_reference: u64, ) -> Result> { let sorted: Vec<_> = incrementals.iter().sorted().collect(); @@ -116,7 +116,6 @@ pub fn resolve_undefined( let aslr_offset = match triple.architecture { target_lexicon::Architecture::Wasm32 => 0, _ => { - let aslr_reference = aslr_reference.unwrap(); aslr_reference - symbol_table .get("_aslr_reference") diff --git a/packages/subsecond/subsecond-cli/src/index.html b/packages/subsecond/subsecond-cli/src/index.html index d130cb6195..2d95e45c62 100644 --- a/packages/subsecond/subsecond-cli/src/index.html +++ b/packages/subsecond/subsecond-cli/src/index.html @@ -5,46 +5,6 @@ Subsecond + + + + +
+ + diff --git a/packages/cli/assets/web/loading.html b/packages/cli/assets/web/dev.loading.html similarity index 100% rename from packages/cli/assets/web/loading.html rename to packages/cli/assets/web/dev.loading.html diff --git a/packages/cli/assets/web/index.html b/packages/cli/assets/web/index.html deleted file mode 100644 index e07ae83045..0000000000 --- a/packages/cli/assets/web/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - {app_title} - - - - - -
- - diff --git a/packages/cli/assets/web/loading.js b/packages/cli/assets/web/loading.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/cli/assets/web/prod.index.html b/packages/cli/assets/web/prod.index.html new file mode 100644 index 0000000000..937c428f23 --- /dev/null +++ b/packages/cli/assets/web/prod.index.html @@ -0,0 +1,27 @@ + + + {app_title} + + + + + +
+ + + diff --git a/packages/cli/assets/web/toast.html b/packages/cli/assets/web/toast.html deleted file mode 100644 index c7018c485f..0000000000 --- a/packages/cli/assets/web/toast.html +++ /dev/null @@ -1,206 +0,0 @@ - - -
-
- -
-
-
- - -
- -
- - - - - - - -

Your app is being rebuilt.

-
- - -

A non-hot-reloadable change occurred and we must rebuild.

-
-
-
- - \ No newline at end of file diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index f4a5284114..ee9665348c 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -1,8 +1,7 @@ use super::prerender::pre_render_static_routes; -use super::templates::InfoPlistData; use crate::{BuildMode, BuildRequest, Platform, WasmOptConfig}; use crate::{Result, TraceSrc}; -use anyhow::Context; +use anyhow::{bail, Context}; use dioxus_cli_opt::{process_file_to, AssetManifest}; use itertools::Itertools; use manganis::{AssetOptions, JsAssetOptions}; @@ -597,21 +596,26 @@ impl AppBundle { std::fs::write(&patch_file, resolved_patch_bytes)?; let linker = match self.build.platform { + Platform::Web => self.build.krate.workspace.wasm_ld(), Platform::Android => { let tools = crate::build::android_tools().context("Could not determine android tools")?; tools.android_cc(&self.build.target) } - Platform::Web => PathBuf::from("wasm-ld"), - Platform::MacOS => PathBuf::from("cc"), - Platform::Ios => PathBuf::from("cc"), - Platform::Windows => todo!(), - Platform::Linux => todo!(), - Platform::Server => todo!(), - Platform::Liveview => todo!(), + + // Note that I think rust uses rust-lld + // https://blog.rust-lang.org/2024/05/17/enabling-rust-lld-on-linux.html + Platform::MacOS + | Platform::Ios + | Platform::Linux + | Platform::Server + | Platform::Liveview => PathBuf::from("cc"), + + // I think this is right?? does windows use cc? + Platform::Windows => PathBuf::from("cc"), }; - let thin_args = self.thin_link_args(&args); + let thin_args = self.thin_link_args(&args, aslr_reference)?; // let mut env_vars = vec![]; // self.build.build_android_env(&mut env_vars, false)?; @@ -639,10 +643,12 @@ impl AppBundle { if !self.patch_exe().exists() { tracing::error!("Failed to generate patch: {}", errs.trim()); } else { - tracing::debug!("Warnigns during linking: {}", errs.trim()); + tracing::debug!("Warnings during thin linking: {}", errs.trim()); } } + if self.build.platform == Platform::Web {} + // // Clean up the temps manually // // todo: we might want to keep them around for debugging purposes // for file in object_files { @@ -661,7 +667,7 @@ impl AppBundle { Ok(()) } - fn thin_link_args(&self, original_args: &[&str]) -> Vec { + fn thin_link_args(&self, original_args: &[&str], aslr_reference: u64) -> Result> { use target_lexicon::OperatingSystem; let triple = self.build.target.clone(); @@ -670,7 +676,42 @@ impl AppBundle { tracing::debug!("original args:\n{}", original_args.join("\n")); match triple.operating_system { + // wasm32-unknown-unknown + // use wasm-ld (gnu-lld) + OperatingSystem::Unknown if self.build.platform == Platform::Web => { + const WASM_PAGE_SIZE: u64 = 65536; + let table_base = 2000 * (aslr_reference + 1); + let global_base = + ((aslr_reference * WASM_PAGE_SIZE * 3) + (WASM_PAGE_SIZE * 32)) as i32; + tracing::info!( + "using aslr of table: {} and global: {}", + table_base, + global_base + ); + + args.extend([ + // .arg("-z") + // .arg("stack-size=1048576") + "--import-memory".to_string(), + "--import-table".to_string(), + "--growable-table".to_string(), + "--export".to_string(), + "main".to_string(), + "--export-all".to_string(), + "--stack-first".to_string(), + "--allow-undefined".to_string(), + "--no-demangle".to_string(), + "--no-entry".to_string(), + "--emit-relocs".to_string(), + // todo: we need to modify the post-processing code + format!("--table-base={}", table_base).to_string(), + format!("--global-base={}", global_base).to_string(), + ]); + } + // this uses "cc" and these args need to be ld compatible + // aarch64-apple-ios + // aarch64-apple-darwin OperatingSystem::IOS(_) | OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) => { args.extend([ "-Wl,-dylib".to_string(), @@ -694,7 +735,7 @@ impl AppBundle { // android/linux // need to be compatible with lld - OperatingSystem::Linux => { + OperatingSystem::Linux if triple.environment == Environment::Android => { args.extend( [ "-shared".to_string(), @@ -714,50 +755,11 @@ impl AppBundle { "-Wl,-z,relro,-z,now".to_string(), "-nodefaultlibs".to_string(), "-Wl,-Bdynamic".to_string(), - // "-Wl,-z,relro,-z,now".to_string(), - // "-nodefaultlibs".to_string(), - // dynamically link against libdioxusmain.so - // format!("-L{}", self.main_exe().parent().unwrap().display()), - // "-ldioxusmain".to_string(), - // "-Wl,-" - // "-Wl,--unresolved-symbols=ignore-all", - // "-Wl,--eh-frame-hdr".to_string(), - // "-Wl,-z,noexecstack".to_string(), - // "-Wl,-u,__rdl_alloc".to_string(), - // "-Wl,-u,__rdl_dealloc".to_string(), - // "-Wl,-u,__rdl_realloc".to_string(), - // "-Wl,-u,__rdl_alloc_zeroed".to_string(), - // "-Wl,-u,__rg_oom".to_string(), - // "-Wl,--allow-shlib-undefined".to_string(), - // "-Wl,--defsym,__rdl_alloc=0".to_string(), - // "-Wl,--defsym,__rdl_dealloc=0".to_string(), - // "-Wl,--defsym,__rdl_realloc=0".to_string(), - // "-Wl,--defsym,__rdl_alloc_zeroed=0".to_string(), - // "-Wl,--defsym,__rg_oom=0".to_string(), ] .iter() .map(|s| s.to_string()), ); - // args.extend([ - // "-fno-pie".to_string(), - // "-landroid".to_string(), - // "-llog".to_string(), - // "-lOpenSLES".to_string(), - // "-Wl,--export-dynamic".to_string(), - // "-Wl,-shared".to_string(), - // // "-Wl,--shared".to_string(), - // "-Wl,--export-dynamic".to_string(), - // "-Wl,--allow-shlib-undefined".to_string(), - // // "-Wl,--unresolved-symbols=ignore-in-object-files".to_string(), - // // "-Wl,-unexported_symbol,_main".to_string(), - // // "-Wl,-undefined,dynamic_lookup".to_string(), - // // "-Wl,--unexported-symbol,_main".to_string(), - // // "-Wl,--export-dynamic".to_string(), - // // "-Wl,-undefined,dynamic_lookup".to_string(), - // // "-Wl,--unresolved-symbols=ignore-all".to_string(), - // ]); - match triple.architecture { target_lexicon::Architecture::Aarch64(_) => { // args.push("-Wl,--target=aarch64-linux-android".to_string()); @@ -769,15 +771,19 @@ impl AppBundle { } } - OperatingSystem::Windows => todo!(), - - // use wasm-ld (gnu-lld) - OperatingSystem::Unknown if self.build.platform == Platform::Web => todo!(), + OperatingSystem::Linux => { + args.extend([ + "-Wl,--eh-frame-hdr".to_string(), + "-Wl,-z,noexecstack".to_string(), + "-Wl,-z,relro,-z,now".to_string(), + "-nodefaultlibs".to_string(), + "-Wl,-Bdynamic".to_string(), + ]); + } - OperatingSystem::Unknown => todo!(), + OperatingSystem::Windows => {} - // Lots of other OSes we don't support yet - tvos, watchos, etc - _ => todo!(), + _ => return Err(anyhow::anyhow!("Unsupported platform for thin linking").into()), } let extract_value = |arg: &str| -> Option { @@ -799,7 +805,7 @@ impl AppBundle { tracing::info!("final args:{:#?}", args); - args + Ok(args) } /// The item that we'll try to run directly if we need to. @@ -1251,3 +1257,11 @@ impl AppBundle { Ok(()) } } + +#[derive(serde::Serialize)] +pub struct InfoPlistData { + pub display_name: String, + pub bundle_name: String, + pub bundle_identifier: String, + pub executable_name: String, +} diff --git a/packages/cli/src/build/mod.rs b/packages/cli/src/build/mod.rs index e853e9cc95..59ea259ee6 100644 --- a/packages/cli/src/build/mod.rs +++ b/packages/cli/src/build/mod.rs @@ -8,19 +8,16 @@ mod builder; mod bundle; mod patch; -mod plan; mod platform; mod prerender; mod progress; mod request; -mod templates; mod verify; mod web; pub(crate) use builder::*; pub(crate) use bundle::*; pub(crate) use patch::*; -pub(crate) use plan::*; pub(crate) use platform::*; pub(crate) use progress::*; pub(crate) use request::*; diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 58981ff456..ee0051aef8 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -29,734 +29,3 @@ pub enum ReloadKind { pub struct PatchData { pub direct_rustc: Vec, } - -// pub async fn attempt_partial_link( -// linker: PathBuf, -// work_dir: PathBuf, -// old_cache: PathBuf, -// new_cache: PathBuf, -// proc_main_addr: u64, -// patch_target: PathBuf, -// out_path: PathBuf, -// ) { -// let mut object = ObjectDiff::new(old_cache, new_cache).unwrap(); -// object.load().unwrap(); - -// let all_exports = object -// .new -// .iter() -// .flat_map(|(_, f)| f.file.exports().unwrap()) -// .map(|e| e.name()) -// .collect::>(); - -// let mut adrp_imports = HashSet::new(); - -// let mut satisfied_exports = HashSet::new(); - -// let modified_symbols = object -// .modified_symbols -// .iter() -// .map(|f| f.as_str()) -// .collect::>(); - -// if modified_symbols.is_empty() { -// println!("No modified symbols"); -// } - -// let mut modified_log = String::new(); -// for m in modified_symbols.iter() { -// // if m.starts_with("l") { -// // continue; -// // } - -// let path = object.find_path_to_main(m); -// println!("m: {m}"); -// println!("path: {path:#?}\n"); -// modified_log.push_str(&format!("{m}\n")); -// modified_log.push_str(&format!("{path:#?}\n")); -// } - -// let modified = object -// .modified_files -// .iter() -// .sorted_by(|a, b| a.0.cmp(&b.0)) -// .collect::>(); - -// // Figure out which symbols are required from *existing* code -// // We're going to create a stub `.o` file that satisfies these by jumping into the original code via a dynamic lookup / and or literally just manually doing it -// for fil in modified.iter() { -// let f = object -// .new -// .get(fil.0.file_name().unwrap().to_str().unwrap()) -// .unwrap(); - -// for i in f.file.imports().unwrap() { -// if all_exports.contains(i.name()) { -// adrp_imports.insert(i.name()); -// } -// } - -// for e in f.file.exports().unwrap() { -// satisfied_exports.insert(e.name()); -// } -// } - -// // Remove any imports that are indeed satisifed -// for s in satisfied_exports.iter() { -// adrp_imports.remove(s); -// } - -// // // Assemble the stub -// // let stub_data = make_stub_file(proc_main_addr, patch_target, adrp_imports); -// // let stub_file = work_dir.join("stub.o"); -// // std::fs::write(&stub_file, stub_data).unwrap(); - -// // let out = Command::new("cc") -// // .args(modified.iter().map(|(f, _)| f)) -// // .arg(stub_file) -// // .arg("-dylib") -// // .arg("-Wl,-undefined,dynamic_lookup") -// // .arg("-Wl,-unexported_symbol,_main") -// // .arg("-arch") -// // .arg("arm64") -// // .arg("-dead_strip") -// // .arg("-o") -// // .arg(out_path) -// // .output() -// // .await -// // .unwrap(); - -// // -O0 ? supposedly faster -// // -reproducible - even better? -// // -exported_symbol and friends - could help with dead-code stripping -// // -e symbol_name - for setting the entrypoint -// // -keep_relocs ? - -// // run the linker, but unexport the `_main` symbol -// let out = Command::new("cc") -// .args(modified.iter().map(|(f, _)| f)) -// .arg("-Wl,-dylib") -// .arg("-Wl,-undefined,dynamic_lookup") -// // .arg("-Wl,-unexported_symbol,_main") -// // .arg("-Wl,-export-dynamic") -// .arg("-arch") -// .arg("arm64") -// .arg("-dead_strip") // maybe? -// .arg("-o") -// .arg(&out_path) -// // .stdout(Stdio::piped()) -// // .stderr(Stdio::piped()) -// .output() -// .await -// .unwrap(); - -// let err = String::from_utf8_lossy(&out.stderr); -// println!("err: {err}"); -// std::fs::write(work_dir.join("link_errs_partial.txt"), &*err).unwrap(); -// } - -// /// todo: detect if the user specified a custom linker -// fn system_linker(platform: Platform) -> &'static str { -// match platform { -// // mac + linux use just CC unless the user is trying to use something like mold / lld -// Platform::MacOS => "cc", -// Platform::Linux => "cc", -// Platform::Windows => "cc", -// Platform::Ios => "cc", -// Platform::Android => "cc", -// Platform::Server => "cc", -// Platform::Liveview => "cc", -// Platform::Web => "wasm-ld", -// } -// } - -// struct ObjectDiff { -// old: BTreeMap, -// new: BTreeMap, -// modified_files: HashMap>, -// modified_symbols: HashSet, -// parents: HashMap>, -// } - -// impl ObjectDiff { -// fn new(old_cache: PathBuf, new_cache: PathBuf) -> Result { -// Ok(Self { -// old: LoadedFile::from_dir(&old_cache)?, -// new: LoadedFile::from_dir(&new_cache)?, -// modified_files: Default::default(), -// modified_symbols: Default::default(), -// parents: Default::default(), -// }) -// } - -// fn load(&mut self) -> Result<()> { -// let num_right = self.new.len(); - -// let keys = self.new.keys().cloned().collect::>(); -// for (idx, f) in keys.iter().enumerate() { -// println!("----- {:?} {}/{} -----", f, idx, num_right); - -// let changed_before = self.modified_symbols.len(); -// self.load_file(f)?; -// let changed_after = self.modified_symbols.len(); - -// if changed_after > changed_before { -// println!("❌ -> {}", changed_after - changed_before); -// } -// } - -// Ok(()) -// } - -// /// Walk the call to find the path to the main function -// fn find_path_to_main(&self, name: &str) -> Vec { -// let mut path = Vec::new(); -// let mut visited = std::collections::HashSet::new(); - -// // Helper function for DFS with backtracking -// fn dfs( -// current: &str, -// path: &mut Vec, -// visited: &mut std::collections::HashSet, -// parents: &std::collections::HashMap>, -// ) -> bool { -// // If we've found main, we're done -// if current.ends_with("_main") { -// path.push(current.to_string()); -// return true; -// } - -// // Mark current node as visited -// visited.insert(current.to_string()); -// path.push(current.to_string()); - -// // Check all parents of the current node -// if let Some(parent_nodes) = parents.get(current) { -// for parent in parent_nodes { -// if !visited.contains(parent) { -// if dfs(parent, path, visited, parents) { -// return true; -// } -// } -// } -// } - -// // If no path is found through this node, backtrack -// path.pop(); - -// false -// } - -// // Start DFS from the given name -// dfs(name, &mut path, &mut visited, &self.parents); - -// path -// } - -// fn load_file(&mut self, name: &str) -> Result<()> { -// let new = &self.new[name]; -// let Some(old) = self.old.get(name) else { -// self.modified_files.entry(new.path.clone()).or_default(); -// return Ok(()); -// }; - -// let mut changed_list = HashSet::new(); -// for section in new.file.sections() { -// let n = section.name().unwrap(); -// if n == "__text" -// || n == "__const" -// || n.starts_with("__literal") -// || n == "__eh_frame" -// || n == "__compact_unwind" -// || n == "__gcc_except_tab" -// || n == "__common" -// || n == "__bss" -// { -// changed_list.extend(self.accumulate_changed(&old, &new, section.index())); -// } else { -// println!("Skipping section: {n}"); -// } -// } - -// for c in changed_list.iter() { -// if !c.starts_with("l") && !c.starts_with("ltmp") { -// self.modified_symbols.insert(c.to_string()); -// } else { -// let mod_name = format!("{c}_{name}"); -// self.modified_symbols.insert(mod_name); -// } -// } - -// for (child, parents) in new.parents.iter() { -// let child_name = match child.starts_with("l") { -// true => format!("{child}_{name}"), -// false => child.to_string(), -// }; - -// for parent in parents { -// let p_name = match parent.starts_with("l") { -// true => format!("{parent}_{name}"), -// false => parent.to_string(), -// }; - -// self.parents -// .entry(child_name.clone()) -// .or_default() -// .insert(p_name); -// } -// } - -// Ok(()) -// } - -// fn accumulate_changed( -// &self, -// old: &LoadedFile, -// new: &LoadedFile, -// section_idx: SectionIndex, -// ) -> HashSet<&'static str> { -// let mut local_modified = HashSet::new(); - -// // Accumulate modified symbols using masking in functions -// let relocated_new = acc_symbols(&new.file, section_idx); -// let mut relocated_old = acc_symbols(&old.file, section_idx) -// .into_iter() -// .map(|f| (f.name, f)) -// .collect::>(); - -// for right in relocated_new { -// let Some(left) = relocated_old.remove(right.name) else { -// local_modified.insert(right.name); -// continue; -// }; - -// // If the contents of the assembly changed, track it -// if !compare_masked(old.file, new.file, &left, &right) { -// local_modified.insert(left.name); -// local_modified.insert(right.name); -// } -// } - -// local_modified -// } -// } - -// /// A file loaded into memory with its analysis -// /// -// /// We leak the module to make it easier to deal with its contents -// struct LoadedFile { -// path: PathBuf, -// open_file: std::fs::File, -// mmap: &'static Mmap, - -// file: &'static File<'static>, - -// // symbol -> symbols -// parents: HashMap<&'static str, HashSet<&'static str>>, -// } - -// impl LoadedFile { -// fn from_dir(dir: &Path) -> anyhow::Result> { -// std::fs::read_dir(dir)? -// .into_iter() -// .flatten() -// .filter(|e| e.path().extension() == Some(OsStr::new("o"))) -// .map(|e| { -// Ok(( -// e.path().file_name().unwrap().to_string_lossy().to_string(), -// Self::new(e.path())?, -// )) -// }) -// .collect() -// } - -// fn new(path: PathBuf) -> anyhow::Result { -// let open_file = std::fs::File::open(&path)?; -// let mmap = unsafe { MmapOptions::new().map(&open_file).unwrap() }; -// let mmap: &'static Mmap = Box::leak(Box::new(mmap)); -// let f = File::parse(mmap.deref() as &[u8])?; -// let file: &'static File<'static> = Box::leak(Box::new(f)); - -// // Set up the data structures -// let mut sym_tab = HashMap::<&'static str, RelocatedSymbol<'static>>::new(); -// let mut parents = HashMap::<&'static str, HashSet<&'static str>>::new(); - -// // Build the symbol table -// for sect in file.sections() { -// for r in acc_symbols(&file, sect.index()) { -// sym_tab.insert(r.name, r); -// } -// } - -// // Create a map of address -> symbol so we can resolve the section of a symbol -// let local_defs = file -// .symbols() -// .filter(|s| s.is_definition()) -// .map(|s| (s.address(), s.name().unwrap())) -// .collect::>(); - -// // Build the call graph by walking the relocations -// // We keep track of what calls whata -// for (sym_name, sym) in sym_tab.iter() { -// let sym_section = file.section_by_index(sym.section).unwrap(); -// let sym_data = sym_section.data().unwrap(); - -// for (addr, reloc) in sym.relocations.iter() { -// let target = match symbol_name_of_relo(file, reloc.target()) { -// Some(name) => name, -// None => { -// let addend = u64::from_le_bytes( -// sym_data[*addr as usize..(*addr + 8) as usize] -// .try_into() -// .unwrap(), -// ); -// local_defs.get(&addend).unwrap() -// } -// }; - -// parents.entry(target).or_default().insert(sym_name); -// } -// } - -// Ok(Self { -// path, -// open_file, -// mmap, -// file, -// parents, -// }) -// } -// } - -// /// A function with its relevant relocations to be used for masked comparisons -// struct RelocatedSymbol<'a> { -// name: &'a str, -// /// offset within the section -// offset: usize, -// data: &'a [u8], -// relocations: &'a [(u64, Relocation)], -// sym: object::Symbol<'a, 'a>, -// section: SectionIndex, -// } - -// fn acc_symbols<'a>(new: &'a File<'a>, section_idx: SectionIndex) -> Vec> { -// let mut syms = vec![]; - -// let section = new.section_by_index(section_idx).unwrap(); - -// let sorted = new -// .symbols() -// .filter(|s| s.section_index() == Some(section_idx)) -// .sorted_by(|a, b| { -// let addr = a.address().cmp(&b.address()); -// if addr == Ordering::Equal { -// a.index().0.cmp(&b.index().0) -// } else { -// addr -// } -// }) -// .collect::>(); - -// // todo!!!!!! jon: don't leak this lol -// let relocations = section -// .relocations() -// .sorted_by(|a, b| a.0.cmp(&b.0).reverse()) -// .collect::>() -// .leak(); - -// let data = section.data().unwrap(); - -// // No symbols, no symbols, -// if sorted.is_empty() { -// return vec![]; -// } - -// // The end of the currently analyzed function -// let mut func_end = section.size() as usize; - -// // The idx into the relocation list that applies to this function. We'll march these -// let mut reloc_idx = 0; - -// // Walk in reverse so we can use the text_length as the initial backstop and to match relocation order -// for sym in sorted.into_iter().rev() { -// let sym_offset = sym.address() - section.address(); - -// // Move the head/tail to include the sub-slice of the relocations that apply to this symbol -// let mut reloc_start = None; -// loop { -// // If we've reached the end of the relocations then we're done -// if reloc_idx == relocations.len() { -// break; -// } - -// // relocations behind the symbol start don't apply -// if relocations[reloc_idx].0 < sym_offset { -// break; -// } - -// // Set the head to the first relocation that applies -// if reloc_start.is_none() { -// reloc_start = Some(reloc_idx); -// } - -// reloc_idx += 1; -// } - -// // Identify the instructions that apply to this symbol -// let data = match reloc_start { -// Some(_start) => &data[sym_offset as usize..func_end], -// _ => &[], -// }; - -// // Identify the relocations that apply to this symbol -// let relocations = match reloc_start { -// Some(start) => &relocations[start..reloc_idx], -// None => &[], -// }; - -// syms.push(RelocatedSymbol { -// name: sym.name().unwrap(), -// sym, -// offset: sym_offset as usize, -// data, -// relocations, -// section: section_idx, -// }); - -// func_end = sym_offset as usize; -// } - -// assert_eq!(reloc_idx, relocations.len()); - -// syms -// } - -// /// Compare two sets of bytes, masking out the bytes that are not part of the symbol -// /// This is so we can compare functions with different relocations -// fn compare_masked<'a>( -// old: &impl Object<'a>, -// new: &impl Object<'a>, -// left: &RelocatedSymbol, -// right: &RelocatedSymbol, -// ) -> bool { -// // Make sure the relocations are the same length -// if left.relocations.len() != right.relocations.len() { -// return false; -// } - -// // Make sure the data is the same length -// // If the size changed then the instructions are different (well, not necessarily, but enough) -// if left.data.len() != right.data.len() { -// return false; -// } - -// // Make sure the names match -// if left.name != right.name { -// return false; -// } - -// // We're going to walk from relocation target to target, but since there's no implicit target -// // to start with, we simply use the end of the data -// let mut last = left.data.len(); - -// // Ensure the relocations point to the same symbol -// // Data symbols are special ... todo -// // -// // relocations are in reverse order, so we can also compare the data as we go -// for x in 0..left.relocations.len() { -// // Grab the reloc -// let (l_addr, left_reloc): &(u64, Relocation) = &left.relocations[x]; -// let (_r_addr, right_reloc): &(u64, Relocation) = &right.relocations[x]; - -// // The targets might not be same by index but should resolve to the same *name* -// let left_target: RelocationTarget = left_reloc.target(); -// let right_target: RelocationTarget = right_reloc.target(); - -// // Use the name of the symbol to compare -// // todo: decide if it's internal vs external -// let left_name = symbol_name_of_relo(old, left_target); -// let right_name = symbol_name_of_relo(new, right_target); -// let (Some(left_name), Some(right_name)) = (left_name, right_name) else { -// continue; -// }; - -// // Make sure the names match -// // if the target is a locally defined symbol, then it might be the same -// // todo(jon): hash the masked contents -// if left_name != right_name { -// return false; -// } - -// // Check the data -// // the slice is the end of the relocation to the start of the previous relocation -// let reloc_byte_size = (left_reloc.size() as usize) / 8; -// let start = *l_addr as usize - left.offset as usize + reloc_byte_size; - -// // Some relocations target the same location -// // In these cases, we just continue since we just masked and checked them already -// if (*l_addr as usize - left.offset as usize) == last { -// continue; -// } - -// debug_assert!(start <= last); -// debug_assert!(start <= left.data.len()); - -// if &left.data[start..last] != &right.data[start..last] { -// return false; -// } - -// if left_reloc.flags() != right_reloc.flags() { -// return false; -// } - -// // todo: more checking... the symbols might be local -// last = start - reloc_byte_size; -// } - -// // And a final check to make sure the data is the same -// if left.data[..last] != right.data[..last] { -// return false; -// } - -// true -// } - -// fn symbol_name_of_relo<'a>(obj: &impl Object<'a>, target: RelocationTarget) -> Option<&'a str> { -// match target { -// RelocationTarget::Symbol(symbol_index) => obj -// .symbol_by_index(symbol_index) -// .unwrap() -// .name_bytes() -// .ok() -// .and_then(|s| std::str::from_utf8(s).ok()), -// RelocationTarget::Section(_) => None, -// RelocationTarget::Absolute => None, -// _ => None, -// } -// } - -// fn make_stub_file( -// proc_main_addr: u64, -// patch_target: PathBuf, -// adrp_imports: HashSet<&[u8]>, -// ) -> Vec { -// let data = fs::read(&patch_target).unwrap(); -// let old = File::parse(&data as &[u8]).unwrap(); -// let main_sym = old.symbol_by_name_bytes(b"_main").unwrap(); -// let aslr_offset = proc_main_addr - main_sym.address(); -// let addressed = old -// .symbols() -// .filter_map(|sym| { -// adrp_imports -// .get(sym.name_bytes().ok()?) -// .copied() -// .map(|o| (o, sym.address() + aslr_offset)) -// }) -// .collect::>(); - -// build_stub( -// old.format(), -// old.architecture(), -// old.endianness(), -// addressed, -// ) -// .unwrap() -// } - -// /// Builds an object file that satisfies the imports -// /// -// /// Creates stub functions that jump to known addresses in a target process. -// /// -// /// .section __TEXT,__text -// /// .globl __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E -// /// .p2align 2 -// /// __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E: -// /// // Load 64-bit address using immediate values -// /// movz x9, #0xCDEF // Bottom 16 bits -// /// movk x9, #0x89AB, lsl #16 // Next 16 bits -// /// movk x9, #0x4567, lsl #32 // Next 16 bits -// /// movk x9, #0x0123, lsl #48 // Top 16 bits -// /// -// /// // Branch to the loaded address -// /// br x9 -// fn build_stub( -// format: BinaryFormat, -// architecture: Architecture, -// endian: Endianness, -// adrp_imports: HashMap<&[u8], u64>, -// ) -> Result> { -// use object::{ -// write::{Object, Symbol, SymbolSection}, -// SectionKind, SymbolFlags, SymbolKind, SymbolScope, -// }; - -// // Create a new ARM64 object file -// let mut obj = Object::new(format, architecture, endian); - -// // Add a text section for our trampolines -// let text_section = obj.add_section(Vec::new(), ".text".into(), SectionKind::Text); - -// // For each symbol, create a trampoline that loads the immediate address and jumps to it -// for (name, addr) in adrp_imports { -// let mut trampoline = Vec::new(); - -// // todo: writing these bytes are only good for arm64 -// // -// // -// // Break down the 64-bit address into 16-bit chunks -// let addr0 = (addr & 0xFFFF) as u16; // Bits 0-15 -// let addr1 = ((addr >> 16) & 0xFFFF) as u16; // Bits 16-31 -// let addr2 = ((addr >> 32) & 0xFFFF) as u16; // Bits 32-47 -// let addr3 = ((addr >> 48) & 0xFFFF) as u16; // Bits 48-63 - -// // MOVZ x9, #addr0 -// let movz = 0xD2800009 | ((addr0 as u32) << 5); -// trampoline.extend_from_slice(&movz.to_le_bytes()); - -// // MOVK x9, #addr1, LSL #16 -// let movk1 = 0xF2A00009 | ((addr1 as u32) << 5); -// trampoline.extend_from_slice(&movk1.to_le_bytes()); - -// // MOVK x9, #addr2, LSL #32 -// let movk2 = 0xF2C00009 | ((addr2 as u32) << 5); -// trampoline.extend_from_slice(&movk2.to_le_bytes()); - -// // MOVK x9, #addr3, LSL #48 -// let movk3 = 0xF2E00009 | ((addr3 as u32) << 5); -// trampoline.extend_from_slice(&movk3.to_le_bytes()); - -// // BR x9 - Branch to the address in x9 -// let br: u32 = 0xD61F0120; -// trampoline.extend_from_slice(&br.to_le_bytes()); - -// // Add the trampoline to the text section -// let symbol_offset = obj.append_section_data(text_section, &trampoline, 4); - -// // we are writing this: -// // __ZN93_$LT$generational_box..references..GenerationalRef$LT$R$GT$$u20$as$u20$core..fmt..Display$GT$3fmt17h455abb35572b9c11E -// // -// // but we should be writing this: -// // _$LT$generational_box..references..GenerationalRef$LT$R$GT$$u20$as$u20$core..fmt..Display$GT$::fmt::h455abb35572b9c11 -// // let name = strip_mangled(name); - -// let name = if name.starts_with(b"_") { -// &name[1..] -// } else { -// name -// }; - -// // Add the symbol -// obj.add_symbol(Symbol { -// name: name.into(), -// value: symbol_offset, -// size: trampoline.len() as u64, -// kind: SymbolKind::Text, -// scope: SymbolScope::Dynamic, -// weak: false, -// section: SymbolSection::Section(text_section), -// flags: SymbolFlags::None, -// }); -// } - -// obj.write().context("Failed to write object file") -// } diff --git a/packages/cli/src/build/plan.rs b/packages/cli/src/build/plan.rs deleted file mode 100644 index fb717e2795..0000000000 --- a/packages/cli/src/build/plan.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::{BuildArgs, BuildRequest, Platform}; diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index ef37d40440..5334d530e9 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -173,15 +173,15 @@ impl BuildRequest { let target = match args.args.target { Some(target) => target, None => match platform { - // Generally just use the host's triple for native executables - Platform::MacOS => target_lexicon::HOST, - Platform::Windows => target_lexicon::HOST, - Platform::Linux => target_lexicon::HOST, - Platform::Server => target_lexicon::HOST, - Platform::Liveview => target_lexicon::HOST, + // Generally just use the host's triple for native executables unless specified otherwisea + Platform::MacOS + | Platform::Windows + | Platform::Linux + | Platform::Server + | Platform::Liveview => target_lexicon::HOST, Platform::Web => "wasm32-unknown-unknown".parse().unwrap(), - // For iOS we should prefer the architecture for the simulator, but in lieu of actually + // For iOS we should prefer the actual architecture for the simulator, but in lieu of actually // figuring that out, we'll assume aarch64 on m-series and x86_64 otherwise Platform::Ios => { // use the host's architecture and sim if --device is passed @@ -197,18 +197,6 @@ impl BuildRequest { // Same idea with android but we figure out the connected device using adb // for now we use Platform::Android => { - // use the host's architecture and sim if --device is passed - // use target_lexicon::{Architecture, HOST}; - // match HOST.architecture { - // Architecture::Aarch64(_) if device => { - // "aarch64-linux-android".parse().unwrap() - // } - // Architecture::Aarch64(_) => "aarch64-linux-android".parse().unwrap(), - // _ if device => "x86_64-linux-android".parse().unwrap(), - // _ => "x86_64-linux-android".parse().unwrap(), - // } - // - // ... for known we don't know the architecture and we'll discover it during compilation "aarch64-linux-android".parse().unwrap() // "unknown-linux-android".parse().unwrap() } @@ -540,12 +528,6 @@ impl BuildRequest { "-Clink-dead-code".to_string(), ]); - // args.extend([ - // "-Wl,--whole-archive".to_string(), - // "-Wl,--no-gc-sections".to_string(), - // "-Wl,--export-all".to_string(), - // ]); - match self.platform { // if macos/ios, -Wl,-all_load is required for the linker to work correctly // macos uses ld64 but through the `cc` interface.a @@ -565,27 +547,34 @@ impl BuildRequest { // if windows -Wl,--whole-archive is required for the linker to work correctly // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170 Platform::Windows => { - // --export-dynamic - cargo_args.push( - "-Clink-args=-Wl,--whole-archive".to_string(), - // "-Clink-args=-Wl,--whole-archive,-Wl,--export-dynamic".to_string(), - ); + cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); } // if web, -Wl,--whole-archive is required for the linker to work correctly. - // We also use --no-gc-sections and --export-all to push every symbol into the export table. + // We also use --no-gc-sections and --export-table and --export-memory to push + // said symbols into the export table. + // + // We use --emit-relocs but scrub those before they make it into the final output. + // This is designed for us to build a solid call graph. // - // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld): + // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld with a -target): // /Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld + // /Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld // // export all should place things like env.memory into the export table so we can access them // when loading the patches Platform::Web => { - cargo_args.push( - "-Clink-args=-Wl,--whole-archive,-Wl,--no-gc-sections,-Wl,--export-all" - .to_string(), - ); + cargo_args.push("-Clink-arg=--no-gc-sections".into()); + cargo_args.push("-Clink-arg=--growable-table".into()); + cargo_args.push("-Clink-arg=--whole-archive".into()); + cargo_args.push("-Clink-arg=--export-table".into()); + cargo_args.push("-Clink-arg=--export-memory".into()); + cargo_args.push("-Clink-arg=--emit-relocs".into()); + cargo_args.push("-Clink-arg=--export=__stack_pointer".into()); + cargo_args.push("-Clink-arg=--export=__heap_base".into()); + cargo_args.push("-Clink-arg=--export=__data_end".into()); } + _ => {} } } @@ -750,7 +739,7 @@ impl BuildRequest { &self, env_vars: &mut Vec<(&str, String)>, rustf_flags: bool, - ) -> Result { + ) -> Result { let tools = crate::build::android_tools().context("Could not determine android tools")?; let linker = tools.android_cc(&self.target); let min_sdk_version = tools.min_sdk_version(); @@ -1023,8 +1012,7 @@ impl BuildRequest { tracing::debug!("Initialized app/src/assets: {:?}", app_assets); tracing::debug!("Initialized app/src/kotlin/main: {:?}", app_kotlin_out); - // handlerbars - let hbs = handlebars::Handlebars::new(); + // handlebars #[derive(serde::Serialize)] struct HbsTypes { application_id: String, @@ -1034,6 +1022,7 @@ impl BuildRequest { application_id: self.krate.full_mobile_app_name(), app_name: self.krate.bundled_app_name(), }; + let hbs = handlebars::Handlebars::new(); // Top-level gradle config write( @@ -1197,22 +1186,22 @@ impl BuildRequest { pub(crate) fn is_patch(&self) -> bool { matches!(&self.mode, BuildMode::Thin { .. }) } - - // pub(crate) fn triple(&self) -> Triple { - // match self.platform { - // Platform::MacOS => Triple::from_str("aarc64-apple-darwin").unwrap(), - // Platform::Windows => Triple::from_str("x86_64-pc-windows-msvc").unwrap(), - // Platform::Linux => Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), - // Platform::Web => Triple::from_str("wasm32-unknown-unknown").unwrap(), - // Platform::Ios => Triple::from_str("aarch64-apple-ios-sim").unwrap(), - // Platform::Android => Triple::from_str("aarch64-linux-android").unwrap(), - // Platform::Server => Triple::from_str("aarc64-apple-darwin").unwrap(), - // // Platform::Server => Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), - // Platform::Liveview => Triple::from_str("aarc64-apple-darwin").unwrap(), - // } - // } } +// pub(crate) fn triple(&self) -> Triple { +// match self.platform { +// Platform::MacOS => Triple::from_str("aarc64-apple-darwin").unwrap(), +// Platform::Windows => Triple::from_str("x86_64-pc-windows-msvc").unwrap(), +// Platform::Linux => Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), +// Platform::Web => Triple::from_str("wasm32-unknown-unknown").unwrap(), +// Platform::Ios => Triple::from_str("aarch64-apple-ios-sim").unwrap(), +// Platform::Android => Triple::from_str("aarch64-linux-android").unwrap(), +// Platform::Server => Triple::from_str("aarc64-apple-darwin").unwrap(), +// // Platform::Server => Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), +// Platform::Liveview => Triple::from_str("aarc64-apple-darwin").unwrap(), +// } +// } + // pub(crate) async fn autodetect_android_arch() -> Option { // // Try auto detecting arch through adb. // static AUTO_ARCH: OnceCell> = OnceCell::new(); diff --git a/packages/cli/src/build/templates.rs b/packages/cli/src/build/templates.rs deleted file mode 100644 index 1f32bf57de..0000000000 --- a/packages/cli/src/build/templates.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(serde::Serialize)] -pub struct InfoPlistData { - pub display_name: String, - pub bundle_name: String, - pub bundle_identifier: String, - pub executable_name: String, -} diff --git a/packages/cli/src/build/web.rs b/packages/cli/src/build/web.rs index 0cc4f883a8..83de2da433 100644 --- a/packages/cli/src/build/web.rs +++ b/packages/cli/src/build/web.rs @@ -7,8 +7,7 @@ use std::path::{Path, PathBuf}; use super::AppBundle; -const DEFAULT_HTML: &str = include_str!("../../assets/web/index.html"); -const TOAST_HTML: &str = include_str!("../../assets/web/toast.html"); +const DEFAULT_HTML: &str = include_str!("../../assets/web/dev.index.html"); impl AppBundle { pub(crate) fn prepare_html(&self) -> Result { @@ -155,15 +154,8 @@ r#" - {DX_TOAST_UTILITIES} html.replace("{DX_TOAST_UTILITIES}", TOAST_HTML), - false => html.replace("{DX_TOAST_UTILITIES}", ""), - }; } /// Replace any special placeholders in the HTML with resolved values diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index 367eecbf19..941b79cdb2 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -38,7 +38,7 @@ pub(crate) struct Autoformat { } impl Autoformat { - pub(crate) fn autoformat(self) -> Result { + pub(crate) async fn autoformat(self) -> Result { let Autoformat { check, raw, @@ -67,8 +67,9 @@ impl Autoformat { package: Some(package), ..Default::default() }; - let dx_crate = - DioxusCrate::new(&target_args).context("failed to parse crate graph")?; + let dx_crate = DioxusCrate::new(&target_args) + .await + .context("failed to parse crate graph")?; Cow::Owned(dx_crate.crate_dir()) } else { diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 06706d84c7..47decfd426 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,9 +1,4 @@ -use std::{path::Path, str::FromStr}; - -use target_lexicon::Triple; - -use super::{chained_command::ChainedCommand, *}; -use crate::{Builder, DioxusCrate, Platform, PROFILE_SERVER}; +use crate::{cli::*, Builder, DioxusCrate}; /// Build the Rust Dioxus app and all of its assets. /// @@ -72,7 +67,9 @@ impl BuildArgs { pub async fn build(self) -> Result { tracing::info!("Building project..."); - let krate = DioxusCrate::new(&self.args).context("Failed to load Dioxus workspace")?; + let krate = DioxusCrate::new(&self.args) + .await + .context("Failed to load Dioxus workspace")?; let bundle = Builder::start(&krate, &self)?.finish().await?; diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index 1f0fb8a99b..e5269d4992 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -36,7 +36,9 @@ impl Bundle { // todo - maybe not? what if you want a devmode bundle? self.args.args.release = true; - let krate = DioxusCrate::new(&self.args.args).context("Failed to load Dioxus workspace")?; + let krate = DioxusCrate::new(&self.args.args) + .await + .context("Failed to load Dioxus workspace")?; tracing::info!("Building app..."); diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/cli/check.rs index 3c28df0126..ba50ecb6b8 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/cli/check.rs @@ -27,7 +27,7 @@ impl Check { match self.file { // Default to checking the project None => { - let dioxus_crate = DioxusCrate::new(&self.target_args)?; + let dioxus_crate = DioxusCrate::new(&self.target_args).await?; check_project_and_report(dioxus_crate) .await .context("error checking project")?; diff --git a/packages/cli/src/cli/config.rs b/packages/cli/src/cli/config.rs index a31321a2f8..af2217739a 100644 --- a/packages/cli/src/cli/config.rs +++ b/packages/cli/src/cli/config.rs @@ -75,7 +75,7 @@ impl From for bool { } impl Config { - pub(crate) fn config(self) -> Result { + pub(crate) async fn config(self) -> Result { let crate_root = crate_root()?; match self { Config::Init { @@ -100,13 +100,15 @@ impl Config { Config::FormatPrint {} => { tracing::info!( "{:#?}", - crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default())?.config + crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default()) + .await? + .config ); } Config::CustomHtml {} => { let html_path = crate_root.join("index.html"); let mut file = File::create(html_path)?; - let content = include_str!("../../assets/web/index.html"); + let content = include_str!("../../assets/web/dev.index.html"); file.write_all(content.as_bytes())?; tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Create custom html file done."); } diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index 896c642d99..24f42e8408 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -110,28 +110,6 @@ impl LinkAction { } } -/// Move all previous object files to "incremental-old" and all new object files to "incremental-new" -fn cache_incrementals(old: &PathBuf, new: &PathBuf, object_files: &[&String]) { - // Remove the old incremental-old directory if it exists - _ = std::fs::remove_dir_all(&old); - - // Rename incremental-new to incremental-old if it exists. Faster than moving all the files - _ = std::fs::rename(&new, &old); - - // Create the new incremental-new directory to place the outputs in - std::fs::create_dir_all(&new).unwrap(); - - // Now drop in all the new object files - for o in object_files.iter() { - if !o.ends_with(".rcgu.o") { - continue; - } - - let path = PathBuf::from(o); - std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); - } -} - /// This creates an object file that satisfies rust's use of llvm-objcopy /// /// I'd rather we *not* do this and instead generate a truly linked file (and then delete it) but @@ -146,7 +124,7 @@ fn make_dummy_object_file(triple: Triple) -> Vec { target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm, target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff, target_lexicon::BinaryFormat::Unknown => todo!(), - _ => todo!(), + _ => todo!("Binary format not supported"), }; let arch = match triple.architecture { @@ -157,21 +135,16 @@ fn make_dummy_object_file(triple: Triple) -> Vec { target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64, target_lexicon::Architecture::LoongArch64 => object::Architecture::LoongArch64, target_lexicon::Architecture::Unknown => object::Architecture::Unknown, - _ => todo!(), + _ => todo!("Architecture not supported"), }; let endian = match triple.endianness() { Ok(target_lexicon::Endianness::Little) => object::Endianness::Little, Ok(target_lexicon::Endianness::Big) => object::Endianness::Big, - Err(_) => todo!(), + Err(_) => todo!("Endianness not supported"), }; object::write::Object::new(format, arch, endian) .write() .unwrap() } - -#[test] -fn creates_dummy_object_file_ios() { - let dummy_object_file = make_dummy_object_file(Platform::Ios); -} diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index bbdf048152..8651f1f24f 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -11,8 +11,9 @@ pub(crate) struct RunArgs { impl RunArgs { pub(crate) async fn run(self) -> Result { - let krate = - DioxusCrate::new(&self.build_args.args).context("Failed to load Dioxus workspace")?; + let krate = DioxusCrate::new(&self.build_args.args) + .await + .context("Failed to load Dioxus workspace")?; tracing::trace!("Building crate krate data: {:#?}", krate); tracing::trace!("Build args: {:#?}", self.build_args); diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index b113d823a5..9c97cb2fea 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -110,7 +110,7 @@ impl ServeArgs { } pub(crate) async fn load_krate(&mut self) -> Result { - let krate = DioxusCrate::new(&self.build_arguments.args)?; + let krate = DioxusCrate::new(&self.build_arguments.args).await?; // Enable hot reload. if self.hot_reload.is_none() { diff --git a/packages/cli/src/dioxus_crate.rs b/packages/cli/src/dioxus_crate.rs index d1678aa477..d132d40701 100644 --- a/packages/cli/src/dioxus_crate.rs +++ b/packages/cli/src/dioxus_crate.rs @@ -1,5 +1,5 @@ -use crate::CliSettings; use crate::{config::DioxusConfig, TargetArgs}; +use crate::{CliSettings, RustcDetails}; use crate::{Platform, Result}; use anyhow::Context; use itertools::Itertools; @@ -20,10 +20,11 @@ pub(crate) static PROFILE_SERVER: &str = "server-dev"; pub struct Workspace { pub(crate) krates: Krates, pub(crate) settings: CliSettings, + pub(crate) rustc: RustcDetails, } impl Workspace { - pub fn load() -> Result> { + pub async fn load() -> Result> { tracing::debug!("Loading workspace"); let cmd = Cmd::new(); let builder = krates::Builder::new(); @@ -32,8 +33,24 @@ impl Workspace { .context("Failed to run cargo metadata")?; let settings = CliSettings::global_or_default(); + let rustc = RustcDetails::from_cli().await?; - Ok(Arc::new(Self { krates, settings })) + Ok(Arc::new(Self { + krates, + settings, + rustc, + })) + } + + pub fn wasm_ld(&self) -> PathBuf { + self.rustc + .sysroot + .join("lib") + .join("rustlib") + .join(Triple::host().to_string()) + .join("bin") + .join("gcc-ld") + .join("wasm-ld") } } @@ -59,25 +76,25 @@ pub(crate) struct DioxusCrate { } impl DioxusCrate { - pub(crate) fn new(target: &TargetArgs) -> Result { - let workspace = Workspace::load()?; + pub(crate) async fn new(args: &TargetArgs) -> Result { + let workspace = Workspace::load().await?; - let package = Self::find_main_package(&workspace.krates, target.package.clone())?; + let package = Self::find_main_package(&workspace.krates, args.package.clone())?; tracing::debug!("Found package {package:?}"); let dioxus_config = DioxusConfig::load(&workspace.krates, package)?.unwrap_or_default(); - let target_kind = match target.example.is_some() { + let target_kind = match args.example.is_some() { true => TargetKind::Example, false => TargetKind::Bin, }; let main_package = &workspace.krates[package]; - let target_name = target + let target_name = args .example .clone() - .or(target.bin.clone()) + .or(args.bin.clone()) .or_else(|| { if let Some(default_run) = &main_package.default_run { return Some(default_run.to_string()); @@ -118,10 +135,10 @@ impl DioxusCrate { target.kind.contains(kind).then_some(target.name.as_str()) }).collect::>(); filtered_packages.join(", ")}; - if let Some(example) = &target.example { + if let Some(example) = &args.example { let examples = target_of_kind(&TargetKind::Example); format!("Failed to find example {example}. \nAvailable examples are:\n{}", examples) - } else if let Some(bin) = &target.bin { + } else if let Some(bin) = &args.bin { let binaries = target_of_kind(&TargetKind::Bin); format!("Failed to find binary {bin}. \nAvailable binaries are:\n{}", binaries) } else { diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 2d9c97fde1..e6025f347f 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -44,8 +44,8 @@ async fn main() { Commands::Translate(opts) => opts.translate(), Commands::New(opts) => opts.create(), Commands::Init(opts) => opts.init(), - Commands::Config(opts) => opts.config(), - Commands::Autoformat(opts) => opts.autoformat(), + Commands::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, diff --git a/packages/cli/src/rustc.rs b/packages/cli/src/rustc.rs index 8ec51558c4..7e5dd4cbee 100644 --- a/packages/cli/src/rustc.rs +++ b/packages/cli/src/rustc.rs @@ -3,7 +3,7 @@ use anyhow::Context; use std::path::PathBuf; use tokio::process::Command; -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct RustcDetails { pub sysroot: PathBuf, pub version: String, diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index b8e5263c79..edd3309339 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -699,7 +699,7 @@ async fn build_status_middleware( if let Some(true) = accepts_html { let status = state.get(); if status != Status::Ready { - let html = include_str!("../../assets/web/loading.html"); + let html = include_str!("../../assets/web/dev.loading.html"); return axum::response::Response::builder() .status(StatusCode::OK) // Load the html loader then keep loading forever diff --git a/packages/subsecond/subsecond-cli/src/index.html b/packages/subsecond/subsecond-cli/src/index.html index 1b36d50d04..0f83a3d188 100644 --- a/packages/subsecond/subsecond-cli/src/index.html +++ b/packages/subsecond/subsecond-cli/src/index.html @@ -29,21 +29,17 @@ // base.memory.grow(3)// each page is 64kb, let's grow by 1 page base.__indirect_function_table.grow(2000); - // console.log("Data start: ", data_start, "BSS start: ", bss_start); - // console.log("BSS Start: ", ifunc_length); - // console.log("Memory Width: ", base.memory.buffer.byteLength); - // console.log("ifunc length: ", ifunc_length); let imports = { env: { // __BSS_DATA_START: new WebAssembly.Global({value: "i32", mutable: false }, bss_start), // __RO_DATA_START: new WebAssembly.Global({value: "i32", mutable: false }, data_start), + __DATA_OFFSET: new WebAssembly.Global({value: "i32", mutable: false }, data_start), + __IFUNC_OFFSET: new WebAssembly.Global({value: "i32", mutable: false }, ifunc_length + 1), __indirect_function_table: base.__indirect_function_table, __stack_pointer: base.__stack_pointer, __tls_base: base.__tls_base, memory: base.memory, - __DATA_OFFSET: new WebAssembly.Global({value: "i32", mutable: false }, data_start), - __IFUNC_OFFSET: new WebAssembly.Global({value: "i32", mutable: false }, ifunc_length + 1), }, }; for (const key of Object.keys(base)) { diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs index 5137505c2d..ee83a1b693 100644 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs @@ -17,7 +17,7 @@ fn app() -> Element { div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", div { style: "background-color: red", for x in 0..1 { - Child { id: x + 1, opt: "List entry", color: "gris" } + Child { id: x + 1, opt: "List entry", color: "gri" } } } div { style: "background-color: orange", @@ -42,7 +42,7 @@ fn app() -> Element { } div { style: "background-color: indigo", for x in 0..1 { - Child { id: x + 10, opt: "List entry", color: "bluebleu" } + Child { id: x + 10, opt: "List entry", color: "magentaaa" } } } } diff --git a/packages/subsecond/subsecond/Cargo.toml b/packages/subsecond/subsecond/Cargo.toml index 4957e638a5..8cb34494d1 100644 --- a/packages/subsecond/subsecond/Cargo.toml +++ b/packages/subsecond/subsecond/Cargo.toml @@ -12,7 +12,8 @@ subsecond-types = { workspace = true } wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } js-sys = { workspace = true} -# serde_wasm_bindgen +serde-wasm-bindgen = { version = "*"} +web-sys = { version = "*", features = ["FetchEvent", "Request", "Window", "Response", "ResponseType"] } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index 41bd950bb1..1d57541f9f 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -209,9 +209,13 @@ use std::{ sync::{Arc, Mutex}, }; -use js_sys::Uint32Array; +use js_sys::{ + ArrayBuffer, Object, Reflect, Uint32Array, Uint8Array, + WebAssembly::{self, Module}, +}; pub use subsecond_macro::hot; pub use subsecond_types::JumpTable; +use wasm_bindgen::UnwrapThrowExt; // todo: if there's a reference held while we run our patch, this gets invalidated. should probably // be a pointer to a jump table instead, behind a cell or something. I believe Atomic + relaxed is basically a no-op @@ -461,6 +465,138 @@ pub async unsafe fn __subsecond_wasm_patch(pointers: Uint32Array) { } unsafe { apply_patch(table) } + + // let table = serde_wasm_bindgen::from_value::(table).unwrap_throw(); + // run_wasm_patch(table).await.unwrap_throw(); +} +// #[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)] +// pub async unsafe fn __subsecond_wasm_patch(table: wasm_bindgen::JsValue) { +// let table = serde_wasm_bindgen::from_value::(table).unwrap_throw(); +// run_wasm_patch(table).await.unwrap_throw(); +// } + +pub async fn run_wasm_patch(table: JumpTable) -> Result<(), wasm_bindgen::JsValue> { + use js_sys::Reflect; + use js_sys::Uint32Array; + use subsecond_types::AddressMap; + use wasm_bindgen::prelude::*; + use wasm_bindgen::JsValue; + use wasm_bindgen_futures::JsFuture; + + const WASM_PAGE_LENGTH: u32 = 65536; + + let funcs: WebAssembly::Table = wasm_bindgen::function_table().unchecked_into(); + let memory: WebAssembly::Memory = wasm_bindgen::memory().unchecked_into(); + let m: WebAssembly::Module = wasm_bindgen::module().unchecked_into(); + let exports: Object = wasm_bindgen::exports().unchecked_into(); + let buffer: Uint8Array = memory.buffer().unchecked_into(); + + let data_start = memory.grow(3) * WASM_PAGE_LENGTH; + let func_start = funcs.grow(2000)?; + let bss_start = memory.grow(3) * WASM_PAGE_LENGTH; + + let imports = Object::new(); + let download = web_sys::window() + .unwrap_throw() + .fetch_with_str(&table.lib.to_str().unwrap_throw()); + + let env = Object::new(); + + // Move exports over + for key in Object::keys(&exports) { + Reflect::set(&env, &key, &Reflect::get(&exports, &key)?)?; + } + + // Set the memory and table in the imports + for (name, value) in [ + ("__BSS_DATA_START", 0), + ("__RO_DATA_START", 0), + ("__DATA_OFFSET", 0), + ("__IFUNC_OFFSET", 0), + ] { + let descripor = Object::new(); + Reflect::set(&descripor, &"value".into(), &"i32".into())?; + Reflect::set(&descripor, &"mutable".into(), &false.into())?; + let value = WebAssembly::Global::new(&descripor, &0.into())?; + Reflect::set(&env, &name.into(), &value)?; + } + + // Set the memory and table in the imports + let imports = Object::new(); + Reflect::set(&imports, &"env".into(), &env)?; + + let module = JsFuture::from(WebAssembly::instantiate_streaming(&download, &imports)).await?; + + // let mut idx = 0; + // for _ in 0..pointers.length() { + // let left = pointers.get_index(idx); + // let right = pointers.get_index(idx + 1); + // table.map.insert(left as u64, right as u64); + // idx += 2 + // } + + // window.patch = patch; + + // // We're going to match up export to export and then ifunc entry to ifunc entry + // // We're going to build a map of old -> new ifunc entries + // const patchExports = patch.instance.exports; + + // let nameToNativeMain = Object.fromEntries( + // Object.keys(wasmExports).map((key) => [key, wasmExports[key].name]).filter(([key, name]) => name !== undefined) + // ); + + // let nameToNativePatch = Object.fromEntries( + // Object.keys(patchExports).map((key) => [key, patchExports[key].name]).filter(([key, name]) => name !== undefined) + // ); + + // let nativeToIndex = Object.fromEntries( + // [...Array(wasmExports.__indirect_function_table.length).keys()].map((i) => { + // let entry = wasmExports.__indirect_function_table.get(i); + // if (entry === null) { + // return ["abcbac", 0]; + // } + // if (entry.name === undefined) { + // return ["abcbac", 0]; + // } + // return [entry.name, i]; + // }) + // ); + + // let jumpTable = Object.fromEntries( + // Object.entries(nameToNativePatch) + // .map(([fnName, nativeName]) => { + // let oldIndex = nativeToIndex[nameToNativeMain[fnName]]; + // let newIndex = nativeToIndex[nativeName]; + // return [fnName, [oldIndex, newIndex]]; + // }) + // .filter(([name, [oldIndex, newIndex]]) => + // oldIndex !== undefined && newIndex !== undefined + // ) + // ); + + // window.jumpTable = jumpTable; + + // let patchList = Object.keys(patchExports).flatMap((key) => { + // let entry = jumpTable[key]; + // if (entry === undefined) { + // return []; + // } + // let a = entry[0]; + // let b = entry[1]; + + // if (a === undefined || b === undefined) { + // return []; + // } + + // // console.log("Patching", key, "from", a, "to", b); + + // return [a, b]; + // }); + // console.log("Patching: ", patchList); + // base["__subsecond_wasm_patch"](patchList); + + // unsafe { apply_patch(table) } + todo!() } /// A trait that enables types to be hot-patched. From 5dc45ae20cf61af17283f07915404737303e0aaf Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 29 Mar 2025 23:18:31 -0600 Subject: [PATCH 048/301] add back assets to dx, fix event in harness --- packages/cli/src/build/bundle.rs | 10 +++++----- packages/subsecond/subsecond-cli/src/main.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index ee9665348c..74174b8507 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -289,11 +289,11 @@ impl AppBundle { .write_main_executable() .await .context("Failed to write main executable")?; - // bundle.write_server_executable().await?; - // bundle - // .write_assets() - // .await - // .context("Failed to write assets")?; + bundle.write_server_executable().await?; + bundle + .write_assets() + .await + .context("Failed to write assets")?; bundle.write_metadata().await?; bundle.optimize().await?; bundle.pre_render_ssg_routes().await?; diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs index a203f6bf7b..f85c00203a 100644 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ b/packages/subsecond/subsecond-cli/src/main.rs @@ -90,7 +90,7 @@ async fn main() -> anyhow::Result<()> { let mut watcher = FsWatcher::watch(src_folder)?; while let Some(Ok(event)) = watcher.rx.next().await { - if event.kind != notify::EventKind::Modify(ModifyKind::Data(DataChange::Content)) { + if event.kind != notify::EventKind::Modify(ModifyKind::Any) { continue; } From 36a2fd48a7538239cb67cc3e70d9240c60792885 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 31 Mar 2025 21:34:10 -0600 Subject: [PATCH 049/301] wip... sadly large refactor --- Cargo.lock | 1 + packages/cli/src/{cli => args}/autoformat.rs | 24 +- packages/cli/src/{cli => args}/build.rs | 75 +- packages/cli/src/{cli => args}/bundle.rs | 42 +- packages/cli/src/args/chained.rs | 191 ++++ packages/cli/src/{cli => args}/check.rs | 15 +- packages/cli/src/{cli => args}/clean.rs | 0 packages/cli/src/{cli => args}/config.rs | 13 +- packages/cli/src/{cli => args}/create.rs | 0 packages/cli/src/{cli => args}/init.rs | 0 packages/cli/src/{cli => args}/link.rs | 9 + packages/cli/src/{cli => args}/mod.rs | 4 +- packages/cli/src/{cli => args}/run.rs | 25 +- packages/cli/src/{cli => args}/serve.rs | 65 +- packages/cli/src/{cli => args}/translate.rs | 0 packages/cli/src/{cli => args}/verbosity.rs | 0 packages/cli/src/build/builder.rs | 78 +- packages/cli/src/build/bundle.rs | 38 +- packages/cli/src/build/mod.rs | 7 + packages/cli/src/build/progress.rs | 1 - packages/cli/src/build/request.rs | 1043 +++++++++++++++--- packages/cli/src/build/verify.rs | 4 +- packages/cli/src/build/web.rs | 16 +- packages/cli/src/cli/chained_command.rs | 190 ---- packages/cli/src/cli/target.rs | 70 -- packages/cli/src/dioxus_crate.rs | 798 -------------- packages/cli/src/main.rs | 7 +- packages/cli/src/serve/handle.rs | 31 +- packages/cli/src/serve/mod.rs | 30 +- packages/cli/src/serve/output.rs | 29 +- packages/cli/src/serve/runner.rs | 24 +- packages/cli/src/serve/server.rs | 9 +- packages/cli/src/serve/state.rs | 91 ++ packages/cli/src/serve/watcher.rs | 7 +- packages/cli/src/workspace.rs | 80 +- packages/web/Cargo.toml | 1 + packages/web/src/devtools.rs | 12 +- 37 files changed, 1539 insertions(+), 1491 deletions(-) rename packages/cli/src/{cli => args}/autoformat.rs (94%) rename packages/cli/src/{cli => args}/build.rs (58%) rename packages/cli/src/{cli => args}/bundle.rs (88%) create mode 100644 packages/cli/src/args/chained.rs rename packages/cli/src/{cli => args}/check.rs (88%) rename packages/cli/src/{cli => args}/clean.rs (100%) rename packages/cli/src/{cli => args}/config.rs (94%) rename packages/cli/src/{cli => args}/create.rs (100%) rename packages/cli/src/{cli => args}/init.rs (100%) rename packages/cli/src/{cli => args}/link.rs (96%) rename packages/cli/src/{cli => args}/mod.rs (97%) rename packages/cli/src/{cli => args}/run.rs (64%) rename packages/cli/src/{cli => args}/serve.rs (76%) rename packages/cli/src/{cli => args}/translate.rs (100%) rename packages/cli/src/{cli => args}/verbosity.rs (100%) delete mode 100644 packages/cli/src/cli/chained_command.rs delete mode 100644 packages/cli/src/cli/target.rs delete mode 100644 packages/cli/src/dioxus_crate.rs create mode 100644 packages/cli/src/serve/state.rs diff --git a/Cargo.lock b/Cargo.lock index da9c0f230f..eb0c74f41b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", + "subsecond", "tracing", "tracing-wasm", "wasm-bindgen", diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/args/autoformat.rs similarity index 94% rename from packages/cli/src/cli/autoformat.rs rename to packages/cli/src/args/autoformat.rs index 941b79cdb2..e13313e4fd 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/args/autoformat.rs @@ -1,5 +1,4 @@ use super::*; -use crate::DioxusCrate; use anyhow::Context; use dioxus_autofmt::{IndentOptions, IndentType}; use rayon::prelude::*; @@ -62,16 +61,17 @@ impl Autoformat { } else { // Default to formatting the project. let crate_dir = if let Some(package) = self.package { - // TODO (matt): Do we need to use the entire `DioxusCrate` here? - let target_args = TargetArgs { - package: Some(package), - ..Default::default() - }; - let dx_crate = DioxusCrate::new(&target_args) - .await - .context("failed to parse crate graph")?; - - Cow::Owned(dx_crate.crate_dir()) + todo!() + // // TODO (matt): Do we need to use the entire `DioxusCrate` here? + // let target_args = TargetArgs { + // package: Some(package), + // ..Default::default() + // }; + // let dx_crate = DioxusCrate::new(&target_args) + // .await + // .context("failed to parse crate graph")?; + + // Cow::Owned(dx_crate.crate_dir()) } else { Cow::Borrowed(Path::new(".")) }; @@ -312,5 +312,5 @@ async fn test_auto_fmt() { package: None, }; - fmt.autoformat().unwrap(); + fmt.autoformat().await.unwrap(); } diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/args/build.rs similarity index 58% rename from packages/cli/src/cli/build.rs rename to packages/cli/src/args/build.rs index 47decfd426..4dc48903b9 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/args/build.rs @@ -1,4 +1,6 @@ -use crate::{cli::*, Builder, DioxusCrate}; +use crate::Platform; +use crate::{args::*, BuildRequest, Builder}; +use target_lexicon::Triple; /// Build the Rust Dioxus app and all of its assets. /// @@ -10,20 +12,65 @@ use crate::{cli::*, Builder, DioxusCrate}; /// ``` #[derive(Clone, Debug, Default, Deserialize, Parser)] pub(crate) struct BuildArgs { - /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] #[clap(long)] + pub(crate) name: Option, + + /// Build for nightly [default: false] + #[clap(long)] + pub(crate) nightly: bool, + + /// Build platform: support Web & Desktop [default: "default_platform"] + #[clap(long, value_enum)] + pub(crate) platform: Option, + + /// Build in release mode [default: false] + #[clap(long, short)] #[serde(default)] - pub(crate) force_sequential: bool, + pub(crate) release: bool, - /// Build the fullstack variant of this app, using that as the fileserver and backend - /// - /// This defaults to `false` but will be overridden to true if the `fullstack` feature is enabled. + /// The package to build + #[clap(short, long)] + pub(crate) package: Option, + + /// Build a specific binary [default: ""] + #[clap(long)] + pub(crate) bin: Option, + + /// Build a specific example [default: ""] + #[clap(long)] + pub(crate) example: Option, + + /// Build the app with custom a profile + #[clap(long)] + pub(crate) profile: Option, + + /// Space separated list of features to activate #[clap(long)] - pub(crate) fullstack: bool, + pub(crate) features: Vec, - /// Run the ssg config of the app and generate the files + /// Don't include the default features in the build #[clap(long)] - pub(crate) ssg: bool, + pub(crate) no_default_features: bool, + + /// Include all features in the build + #[clap(long)] + pub(crate) all_features: bool, + + /// Rustc platform triple + #[clap(long)] + pub(crate) target: Option, + + // todo -- make a subcommand called "--" that takes all the remaining args + /// Extra arguments passed to `rustc` + /// + /// cargo rustc -- -Clinker + #[clap(value_delimiter = ',')] + pub(crate) cargo_args: Vec, + + /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] + #[clap(long)] + #[serde(default)] + pub(crate) force_sequential: bool, /// Skip collecting assets from dependencies [default: false] #[clap(long)] @@ -55,23 +102,17 @@ pub(crate) struct BuildArgs { /// If device is false, then we'll build for the simulator #[clap(long)] pub(crate) device: Option, - - /// Information about the target to build - /// - /// These are the same args as `targets`` - #[clap(flatten)] - pub(crate) args: TargetArgs, } impl BuildArgs { pub async fn build(self) -> Result { tracing::info!("Building project..."); - let krate = DioxusCrate::new(&self.args) + let build = BuildRequest::new(&self) .await .context("Failed to load Dioxus workspace")?; - let bundle = Builder::start(&krate, &self)?.finish().await?; + let bundle = Builder::start(&build)?.finish().await?; tracing::info!(path = ?bundle.build.root_dir(), "Build completed successfully! 🚀"); diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/args/bundle.rs similarity index 88% rename from packages/cli/src/cli/bundle.rs rename to packages/cli/src/args/bundle.rs index e5269d4992..99afede7cf 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/args/bundle.rs @@ -1,4 +1,4 @@ -use crate::{AppBundle, BuildArgs, Builder, DioxusCrate, Platform}; +use crate::{AppBundle, BuildArgs, BuildRequest, Builder, Platform}; use anyhow::{anyhow, Context}; use path_absolutize::Absolutize; use std::collections::HashMap; @@ -6,7 +6,22 @@ use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuild use super::*; -/// Bundle the Rust desktop app and all of its assets +/// Bundle an app and its assets. +/// +/// This only takes a single build into account. To build multiple targets, use multiple calls to bundle. +/// +/// ``` +/// dioxus bundle --target +/// dioxus bundle --target +/// ``` +/// +/// Note that building the server will perform a client build as well: +/// +/// ``` +/// dioxus bundle --platform server +/// ``` +/// +/// This will produce a client `public` folder and the associated server executable in the output folder. #[derive(Clone, Debug, Parser)] pub struct Bundle { /// The package types to bundle @@ -23,6 +38,16 @@ pub struct Bundle { #[clap(long)] pub out_dir: Option, + /// Build the fullstack variant of this app, using that as the fileserver and backend + /// + /// This defaults to `false` but will be overridden to true if the `fullstack` feature is enabled. + #[clap(long)] + pub(crate) fullstack: bool, + + /// Run the ssg config of the app and generate the files + #[clap(long)] + pub(crate) ssg: bool, + /// The arguments for the dioxus build #[clap(flatten)] pub(crate) args: BuildArgs, @@ -34,15 +59,15 @@ impl Bundle { // We always use `release` mode for bundling // todo - maybe not? what if you want a devmode bundle? - self.args.args.release = true; + self.args.release = true; - let krate = DioxusCrate::new(&self.args.args) + let build = BuildRequest::new(&self.args) .await .context("Failed to load Dioxus workspace")?; tracing::info!("Building app..."); - let bundle = Builder::start(&krate, &self.args)?.finish().await?; + let bundle = Builder::start(&build)?.finish().await?; // If we're building for iOS, we need to bundle the iOS bundle if bundle.build.platform == Platform::Ios && self.package_types.is_none() { @@ -61,7 +86,7 @@ impl Bundle { // By default, mac/win/linux work with tauri bundle Platform::MacOS | Platform::Linux | Platform::Windows => { tracing::info!("Running desktop bundler..."); - for bundle in Self::bundle_desktop(&krate, &bundle, &self.package_types)? { + for bundle in Self::bundle_desktop(&bundle, &self.package_types)? { bundles.extend(bundle.bundle_paths); } } @@ -85,7 +110,7 @@ impl Bundle { }; // Copy the bundles to the output directory if one was specified - let crate_outdir = bundle.build.krate.crate_out_dir(); + let crate_outdir = bundle.build.crate_out_dir(); if let Some(outdir) = self.out_dir.clone().or(crate_outdir) { let outdir = outdir .absolutize() @@ -129,10 +154,11 @@ impl Bundle { } fn bundle_desktop( - krate: &DioxusCrate, bundle: &AppBundle, package_types: &Option>, ) -> Result, Error> { + let krate = &bundle.build; + _ = std::fs::remove_dir_all(krate.bundle_dir(bundle.build.platform)); let package = krate.package(); diff --git a/packages/cli/src/args/chained.rs b/packages/cli/src/args/chained.rs new file mode 100644 index 0000000000..148d61adcc --- /dev/null +++ b/packages/cli/src/args/chained.rs @@ -0,0 +1,191 @@ +use clap::{ArgMatches, Args, FromArgMatches, Parser, Subcommand}; +use serde::{de::DeserializeOwned, Deserialize}; + +// https://github.com/clap-rs/clap/issues/2222#issuecomment-2524152894 +// +// +/// `[Args]` wrapper to match `T` variants recursively in `U`. +#[derive(Debug, Clone)] +pub struct ChainedCommand { + /// Specific Variant. + inner: T, + + /// Enum containing `Self` variants, in other words possible follow-up commands. + next: Option>, +} + +impl ChainedCommand +where + T: Args, + U: Subcommand, +{ + fn commands(self) -> Vec { + let mut commands = vec![]; + commands + } +} + +impl Args for ChainedCommand +where + T: Args, + U: Subcommand, +{ + fn augment_args(cmd: clap::Command) -> clap::Command { + // We use the special `defer` method whcih lets us recursively call `augment_args` on the inner command + // and thus `from_arg_matches` + T::augment_args(cmd).defer(|cmd| U::augment_subcommands(cmd.disable_help_subcommand(true))) + } + + fn augment_args_for_update(_cmd: clap::Command) -> clap::Command { + unimplemented!() + } +} + +impl FromArgMatches for ChainedCommand +where + T: Args, + U: Subcommand, +{ + fn from_arg_matches(matches: &ArgMatches) -> Result { + // Parse the first command before we try to parse the next one. + let inner = T::from_arg_matches(matches)?; + + // Try to parse the remainder of the command as a subcommand. + let next = match matches.subcommand() { + // Subcommand skips into the matched .subcommand, hence we need to pass *outer* matches, ignoring the inner matches + // (which in the average case should only match enumerated T) + // + // Here, we might want to eventually enable arbitrary names of subcommands if they're prefixed + // with a prefix like "@" ie `dx serve @dog-app/backend --args @dog-app/frontend --args` + // + // we are done, since sub-sub commmands are matched in U:: + Some(_) => Some(Box::new(U::from_arg_matches(matches)?)), + + // no subcommand matched, we are done + None => None, + }; + + Ok(Self { inner, next }) + } + + fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), clap::Error> { + unimplemented!() + } +} + +impl<'de, T: Deserialize<'de>, U: Deserialize<'de>> Deserialize<'de> for ChainedCommand { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + todo!() + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[derive(Debug, Parser)] +// struct TestCli { +// #[clap(long)] +// top: Option, + +// #[command(subcommand)] +// cmd: TopCmd, +// } + +// /// Launch a specific target +// /// +// /// You can specify multiple targets using `@client --args` syntax. +// #[derive(Debug, Parser)] +// struct ServeCommand { +// #[clap(flatten)] +// args: Target, + +// #[command(subcommand)] +// targets: TopCmd, +// } + +// #[derive(Debug, Subcommand, Clone)] +// enum TopCmd { +// Serve { +// #[clap(subcommand)] +// cmd: Cmd, +// }, +// } + +// /// Launch a specific target +// #[derive(Debug, Subcommand, Clone)] +// #[command(subcommand_precedence_over_arg = true)] +// enum Cmd { +// /// Specify the arguments for the client build +// #[clap(name = "client")] +// Client(ReClap), + +// /// Specify the arguments for the server build +// #[clap(name = "server")] +// Server(ReClap), + +// /// Specify the arguments for any number of additional targets +// #[clap(name = "target")] +// Target(ReClap), +// } + +// #[derive(Clone, Args, Debug)] +// struct Target { +// #[arg(short, long)] +// profile: Option, + +// #[arg(short, long)] +// target: Option, + +// #[arg(short, long)] +// bin: Option, +// } + +// #[test] +// fn test_parse_args() { +// let args = r#" +// dx serve +// @client --release +// @server --target wasm32 +// @target --bin mybin +// @target --bin mybin +// @target --bin mybin +// @target --bin mybin +// "# +// .trim() +// .split_ascii_whitespace(); + +// let cli = TestCli::parse_from(args); + +// dbg!(&cli); + +// match cli.cmd { +// TopCmd::Serve { cmd } => { +// let mut next = Some(cmd); + +// // let mut next = cmd.cmd; +// while let Some(cmd) = next { +// // println!("{cmd:?}"); +// // could use enum_dispatch +// next = match cmd { +// Cmd::Client(rec) => { +// // +// (rec.next).map(|d| *d) +// } +// Cmd::Server(rec) => { +// // +// (rec.next).map(|d| *d) +// } +// Cmd::Target(rec) => { +// // +// (rec.next).map(|d| *d) +// } +// } +// } +// } +// } +// } +// } diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/args/check.rs similarity index 88% rename from packages/cli/src/cli/check.rs rename to packages/cli/src/args/check.rs index ba50ecb6b8..e578d3da71 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/args/check.rs @@ -4,7 +4,6 @@ //! https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs use super::*; -use crate::DioxusCrate; use anyhow::Context; use futures_util::{stream::FuturesUnordered, StreamExt}; use std::path::Path; @@ -18,7 +17,7 @@ pub(crate) struct Check { /// Information about the target to check #[clap(flatten)] - pub(crate) target_args: TargetArgs, + pub(crate) build_args: BuildArgs, } impl Check { @@ -27,8 +26,7 @@ impl Check { match self.file { // Default to checking the project None => { - let dioxus_crate = DioxusCrate::new(&self.target_args).await?; - check_project_and_report(dioxus_crate) + check_project_and_report(&self.build_args) .await .context("error checking project")?; } @@ -52,10 +50,11 @@ async fn check_file_and_report(path: PathBuf) -> Result<()> { /// Runs using Tokio for multithreading, so it should be really really fast /// /// Doesn't do mod-descending, so it will still try to check unreachable files. TODO. -async fn check_project_and_report(dioxus_crate: DioxusCrate) -> Result<()> { - let mut files_to_check = vec![dioxus_crate.main_source_file()]; - collect_rs_files(&dioxus_crate.crate_dir(), &mut files_to_check); - check_files_and_report(files_to_check).await +async fn check_project_and_report(krate: &BuildArgs) -> Result<()> { + todo!("check_project_and_report"); + // let mut files_to_check = vec![dioxus_crate.main_source_file()]; + // collect_rs_files(&dioxus_crate.crate_dir(), &mut files_to_check); + // check_files_and_report(files_to_check).await } /// Check a list of files and report the issues. diff --git a/packages/cli/src/cli/clean.rs b/packages/cli/src/args/clean.rs similarity index 100% rename from packages/cli/src/cli/clean.rs rename to packages/cli/src/args/clean.rs diff --git a/packages/cli/src/cli/config.rs b/packages/cli/src/args/config.rs similarity index 94% rename from packages/cli/src/cli/config.rs rename to packages/cli/src/args/config.rs index af2217739a..30b5e220c7 100644 --- a/packages/cli/src/cli/config.rs +++ b/packages/cli/src/args/config.rs @@ -98,12 +98,13 @@ impl Config { tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Init config file completed."); } Config::FormatPrint {} => { - tracing::info!( - "{:#?}", - crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default()) - .await? - .config - ); + todo!("Load workspace and print its config?") + // tracing::info!( + // "{:#?}", + // crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default()) + // .await? + // .config + // ); } Config::CustomHtml {} => { let html_path = crate_root.join("index.html"); diff --git a/packages/cli/src/cli/create.rs b/packages/cli/src/args/create.rs similarity index 100% rename from packages/cli/src/cli/create.rs rename to packages/cli/src/args/create.rs diff --git a/packages/cli/src/cli/init.rs b/packages/cli/src/args/init.rs similarity index 100% rename from packages/cli/src/cli/init.rs rename to packages/cli/src/args/init.rs diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/args/link.rs similarity index 96% rename from packages/cli/src/cli/link.rs rename to packages/cli/src/args/link.rs index 24f42e8408..6bfc88147f 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/args/link.rs @@ -117,6 +117,8 @@ impl LinkAction { /// /// This is because our host compiler is a stateful server and not a stateless linker. fn make_dummy_object_file(triple: Triple) -> Vec { + let triple = Triple::host(); + let format = match triple.binary_format { target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf, target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff, @@ -148,3 +150,10 @@ fn make_dummy_object_file(triple: Triple) -> Vec { .write() .unwrap() } + +#[test] +fn test_make_dummy_object_file() { + let triple: Triple = "wasm32-unknown-unknown".parse().unwrap(); + let obj = make_dummy_object_file(triple); + assert!(!obj.is_empty()); +} diff --git a/packages/cli/src/cli/mod.rs b/packages/cli/src/args/mod.rs similarity index 97% rename from packages/cli/src/cli/mod.rs rename to packages/cli/src/args/mod.rs index 0b6fbfb0fd..731537f39b 100644 --- a/packages/cli/src/cli/mod.rs +++ b/packages/cli/src/args/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod autoformat; pub(crate) mod build; pub(crate) mod bundle; -pub(crate) mod chained_command; +pub(crate) mod chained; pub(crate) mod check; pub(crate) mod clean; pub(crate) mod config; @@ -10,13 +10,11 @@ pub(crate) mod init; pub(crate) mod link; pub(crate) mod run; pub(crate) mod serve; -pub(crate) mod target; pub(crate) mod translate; pub(crate) mod verbosity; pub(crate) use build::*; pub(crate) use serve::*; -pub(crate) use target::*; pub(crate) use verbosity::*; use crate::{error::Result, Error, StructuredOutput}; diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/args/run.rs similarity index 64% rename from packages/cli/src/cli/run.rs rename to packages/cli/src/args/run.rs index 8651f1f24f..8a6a9af2df 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/args/run.rs @@ -1,5 +1,8 @@ use super::*; -use crate::{serve::HandleUpdate, BuildArgs, BuildRequest, Builder, DioxusCrate, Platform, Result}; +use crate::{ + serve::{AppHandle, HandleUpdate}, + BuildArgs, BuildRequest, Builder, Platform, Result, +}; /// Run the project with the given arguments #[derive(Clone, Debug, Parser)] @@ -11,14 +14,10 @@ pub(crate) struct RunArgs { impl RunArgs { pub(crate) async fn run(self) -> Result { - let krate = DioxusCrate::new(&self.build_args.args) + let build = BuildRequest::new(&self.build_args) .await - .context("Failed to load Dioxus workspace")?; - - tracing::trace!("Building crate krate data: {:#?}", krate); - tracing::trace!("Build args: {:#?}", self.build_args); - - let bundle = Builder::start(&krate, &self.build_args)?.finish().await?; + .context("error building project")?; + let bundle = Builder::start(&build)?.finish().await?; let devserver_ip = "127.0.0.1:8081".parse().unwrap(); let fullstack_ip = "127.0.0.1:8080".parse().unwrap(); @@ -27,15 +26,13 @@ impl RunArgs { tracing::info!("Serving at: {}", fullstack_ip); } - let mut runner = crate::serve::AppRunner::start(&krate); - runner - .open(bundle, devserver_ip, Some(fullstack_ip), true) - .await?; + let mut handle = AppHandle::new(bundle).await?; + handle.open(devserver_ip, Some(fullstack_ip), true).await?; // Run the app, but mostly ignore all the other messages // They won't generally be emitted loop { - match runner.running.as_mut().unwrap().wait().await { + match handle.wait().await { HandleUpdate::StderrReceived { platform, msg } => { tracing::info!("[{platform}]: {msg}") } @@ -43,7 +40,7 @@ impl RunArgs { tracing::info!("[{platform}]: {msg}") } HandleUpdate::ProcessExited { platform, status } => { - runner.cleanup().await; + handle.cleanup().await; tracing::info!("[{platform}]: process exited with status: {status:?}"); break; } diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/args/serve.rs similarity index 76% rename from packages/cli/src/cli/serve.rs rename to packages/cli/src/args/serve.rs index 9c97cb2fea..98a190ea8f 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/args/serve.rs @@ -1,8 +1,24 @@ -use super::{chained_command::ChainedCommand, *}; -use crate::{AddressArguments, BuildArgs, DioxusCrate, Platform, PROFILE_SERVER}; +use super::{chained::ChainedCommand, *}; +use crate::{AddressArguments, BuildArgs, Platform, PROFILE_SERVER}; use target_lexicon::Triple; /// Serve the project +/// +/// `dx serve` takes cargo args by default, except with a required `--platform` arg: +/// +/// ``` +/// dx serve --example blah --target blah --platform android +/// ``` +/// +/// As of dioxus 0.7, `dx serve` allows multiple builds at the same type by chaining the `crate` subcommand: +/// ``` +/// dx serve +/// @crate --blah +/// @crate --blah +/// @crate --blah +/// @crate --blah +/// ``` +/// #[derive(Clone, Debug, Default, Parser)] #[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))] pub(crate) struct ServeArgs { @@ -75,8 +91,8 @@ pub(crate) struct ServeArgs { /// dx serve \ /// client --target aarch64-apple-darwin \ /// server --target wasm32-unknown-unknown \ - /// target --target aarch64-unknown-linux-gnu - /// target --target x86_64-unknown-linux-gnu + /// crate --target aarch64-unknown-linux-gnu + /// crate --target x86_64-unknown-linux-gnu /// ``` #[command(subcommand)] pub(crate) targets: Option, @@ -88,15 +104,15 @@ pub(crate) struct ServeArgs { pub(crate) enum TargetCmd { /// Specify the arguments for the client build #[clap(name = "client")] - Client(ChainedCommand), + Client(ChainedCommand), /// Specify the arguments for the server build #[clap(name = "server")] - Server(ChainedCommand), + Server(ChainedCommand), /// Specify the arguments for any number of additional targets - #[clap(name = "target")] - Target(ChainedCommand), + #[clap(name = "crate")] + Target(ChainedCommand), } impl ServeArgs { @@ -109,39 +125,6 @@ impl ServeArgs { Ok(StructuredOutput::Success) } - pub(crate) async fn load_krate(&mut self) -> Result { - let krate = DioxusCrate::new(&self.build_arguments.args).await?; - - // Enable hot reload. - if self.hot_reload.is_none() { - self.hot_reload = Some(krate.workspace.settings.always_hot_reload.unwrap_or(true)); - } - - // Open browser. - if self.open.is_none() { - self.open = Some( - krate - .workspace - .settings - .always_open_browser - .unwrap_or_default(), - ); - } - - // Set WSL file poll interval. - if self.wsl_file_poll_interval.is_none() { - self.wsl_file_poll_interval = - Some(krate.workspace.settings.wsl_file_poll_interval.unwrap_or(2)); - } - - // Set always-on-top for desktop. - if self.always_on_top.is_none() { - self.always_on_top = Some(krate.workspace.settings.always_on_top.unwrap_or(true)) - } - - Ok(krate) - } - pub(crate) fn should_hotreload(&self) -> bool { self.hot_reload.unwrap_or(true) } diff --git a/packages/cli/src/cli/translate.rs b/packages/cli/src/args/translate.rs similarity index 100% rename from packages/cli/src/cli/translate.rs rename to packages/cli/src/args/translate.rs diff --git a/packages/cli/src/cli/verbosity.rs b/packages/cli/src/args/verbosity.rs similarity index 100% rename from packages/cli/src/cli/verbosity.rs rename to packages/cli/src/args/verbosity.rs diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 82acf6fb23..fd15e0d95e 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1,6 +1,6 @@ use crate::{ - AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, ProgressRx, - ProgressTx, Result, StructuredOutput, + AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, ProgressRx, ProgressTx, Result, + StructuredOutput, }; use std::{ path::PathBuf, @@ -18,8 +18,6 @@ use super::BuildMode; /// Here, we track the number of crates being compiled, assets copied, the times of these events, and /// other metadata that gives us useful indicators for the UI. pub(crate) struct Builder { - // Components of the build - pub krate: DioxusCrate, pub request: BuildRequest, pub build: tokio::task::JoinHandle>, pub tx: ProgressTx, @@ -42,18 +40,16 @@ pub(crate) struct Builder { impl Builder { /// Create a new builder and immediately start a build - pub(crate) fn start(krate: &DioxusCrate, args: &BuildArgs) -> Result { + pub(crate) fn start(request: &BuildRequest) -> Result { let (tx, rx) = futures_channel::mpsc::unbounded(); - let request = BuildRequest::new(args.clone(), krate.clone(), tx.clone(), BuildMode::Fat)?; + // let request = BuildRequest::new(args.clone(), krate.clone(), tx.clone(), BuildMode::Fat)?; Ok(Self { - krate: krate.clone(), request: request.clone(), stage: BuildStage::Initializing, build: tokio::spawn(async move { - // On the first build, we want to verify the tooling... don't bother on subsequent builds - request.verify_tooling().await?; - request.build_all().await + // request.build_all().await + todo!() }), tx, rx, @@ -188,43 +184,45 @@ impl Builder { changed_files: Vec, aslr_offset: u64, ) -> Result<()> { - // Initialize a new build, resetting our progress/stage to the beginning and replacing the old tokio task - let request = BuildRequest::new( - args, - self.krate.clone(), - self.tx.clone(), - BuildMode::Thin { - direct_rustc, - changed_files, - aslr_reference: aslr_offset, - }, - )?; - - // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit - self.abort_all(); - self.request = request.clone(); - self.stage = BuildStage::Restarting; - - // This build doesn't have any extra special logging - rebuilds would get pretty noisy - self.build = tokio::spawn(async move { request.build_all().await }); - - Ok(()) + todo!() + // // Initialize a new build, resetting our progress/stage to the beginning and replacing the old tokio task + // let request = BuildRequest::new( + // args, + // self.krate.clone(), + // self.tx.clone(), + // BuildMode::Thin { + // direct_rustc, + // changed_files, + // aslr_reference: aslr_offset, + // }, + // )?; + + // // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit + // self.abort_all(); + // self.request = request.clone(); + // self.stage = BuildStage::Restarting; + + // // This build doesn't have any extra special logging - rebuilds would get pretty noisy + // self.build = tokio::spawn(async move { request.build_all().await }); + + // Ok(()) } /// Restart this builder with new build arguments. pub(crate) fn rebuild(&mut self, args: BuildArgs) -> Result<()> { - let request = BuildRequest::new(args, self.krate.clone(), self.tx.clone(), BuildMode::Fat)?; + todo!() + // let request = BuildRequest::new(args, self.krate.clone(), self.tx.clone(), BuildMode::Fat)?; - // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit - // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task - self.abort_all(); - self.request = request.clone(); - self.stage = BuildStage::Restarting; + // // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit + // // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task + // self.abort_all(); + // self.request = request.clone(); + // self.stage = BuildStage::Restarting; - // This build doesn't have any extra special logging - rebuilds would get pretty noisy - self.build = tokio::spawn(async move { request.build_all().await }); + // // This build doesn't have any extra special logging - rebuilds would get pretty noisy + // self.build = tokio::spawn(async move { request.build_all().await }); - Ok(()) + // Ok(()) } /// Shutdown the current build process diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index 74174b8507..8a81944949 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -90,7 +90,6 @@ use tokio::process::Command; /// /// ## Extra links /// - xbuild: https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs -#[derive(Debug)] pub(crate) struct AppBundle { pub(crate) build: BuildRequest, pub(crate) app: BuildArtifacts, @@ -475,10 +474,10 @@ impl AppBundle { // prefer to log using a shorter path relative to the workspace dir by trimming the workspace dir let from_ = from - .strip_prefix(self.build.krate.workspace_dir()) + .strip_prefix(self.build.workspace_dir()) .unwrap_or(from.as_path()); let to_ = from - .strip_prefix(self.build.krate.workspace_dir()) + .strip_prefix(self.build.workspace_dir()) .unwrap_or(to.as_path()); tracing::debug!("Copying asset {from_:?} to {to_:?}"); @@ -487,7 +486,7 @@ impl AppBundle { // And then queue the legacy assets // ideally, one day, we can just check the rsx!{} calls for references to assets - for from in self.build.krate.legacy_asset_dir_files() { + for from in self.build.legacy_asset_dir_files() { let to = asset_dir.join(from.file_name().unwrap()); tracing::debug!("Copying legacy asset {from:?} to {to:?}"); assets_to_transfer.push((from, to, AssetOptions::Unknown)); @@ -499,7 +498,7 @@ impl AppBundle { // Parallel Copy over the assets and keep track of progress with an atomic counter let progress = self.build.progress.clone(); - let ws_dir = self.build.krate.workspace_dir(); + let ws_dir = self.build.workspace_dir(); // Optimizing assets is expensive and blocking, so we do it in a tokio spawn blocking task tokio::task::spawn_blocking(move || { assets_to_transfer @@ -596,7 +595,7 @@ impl AppBundle { std::fs::write(&patch_file, resolved_patch_bytes)?; let linker = match self.build.platform { - Platform::Web => self.build.krate.workspace.wasm_ld(), + Platform::Web => self.build.workspace.wasm_ld(), Platform::Android => { let tools = crate::build::android_tools().context("Could not determine android tools")?; @@ -882,7 +881,6 @@ impl AppBundle { // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output let pre_compress = self .build - .krate .should_pre_compress_web_assets(self.build.release); self.build.status_compressing_assets(); @@ -907,10 +905,7 @@ impl AppBundle { pub(crate) fn server_exe(&self) -> Option { if let Some(_server) = &self.server { - let mut path = self - .build - .krate - .build_dir(Platform::Server, self.build.release); + let mut path = self.build.build_dir(Platform::Server, self.build.release); if cfg!(windows) { path.push("server.exe"); @@ -942,7 +937,6 @@ impl AppBundle { let rustc_exe = self.app.exe.with_extension("wasm"); let bindgen_version = self .build - .krate .wasm_bindgen_version() .expect("this should have been checked by tool verification"); @@ -958,7 +952,7 @@ impl AppBundle { // todo(jon): investigate if the chrome extension needs them demangled or demangles them automatically. let will_wasm_opt = (self.build.release || self.build.wasm_split) && crate::wasm_opt::wasm_opt_available(); - let keep_debug = self.build.krate.config.web.wasm_opt.debug + let keep_debug = self.build.config.web.wasm_opt.debug || self.build.debug_symbols || self.build.wasm_split || !self.build.release @@ -967,7 +961,7 @@ impl AppBundle { let wasm_opt_options = WasmOptConfig { memory_packing: self.build.wasm_split, debug: self.build.debug_symbols, - ..self.build.krate.config.web.wasm_opt.clone() + ..self.build.config.web.wasm_opt.clone() }; // Run wasm-bindgen. Some of the options are not "optimal" but will be fixed up by wasm-opt @@ -985,7 +979,7 @@ impl AppBundle { .demangle(demangle) .keep_debug(keep_debug) .keep_lld_sections(true) - .out_name(self.build.krate.executable_name()) + .out_name(self.build.executable_name()) .out_dir(&bindgen_outdir) .remove_name_section(!will_wasm_opt) .remove_producers_section(!will_wasm_opt) @@ -1134,10 +1128,10 @@ impl AppBundle { .render_template( include_str!("../../assets/macos/mac.plist.hbs"), &InfoPlistData { - display_name: self.build.krate.bundled_app_name(), - bundle_name: self.build.krate.bundled_app_name(), + display_name: self.build.bundled_app_name(), + bundle_name: self.build.bundled_app_name(), executable_name: self.build.platform_exe_name(), - bundle_identifier: self.build.krate.bundle_identifier(), + bundle_identifier: self.build.bundle_identifier(), }, ) .map_err(|e| e.into()) @@ -1148,10 +1142,10 @@ impl AppBundle { .render_template( include_str!("../../assets/ios/ios.plist.hbs"), &InfoPlistData { - display_name: self.build.krate.bundled_app_name(), - bundle_name: self.build.krate.bundled_app_name(), + display_name: self.build.bundled_app_name(), + bundle_name: self.build.bundled_app_name(), executable_name: self.build.platform_exe_name(), - bundle_identifier: self.build.krate.bundle_identifier(), + bundle_identifier: self.build.bundle_identifier(), }, ) .map_err(|e| e.into()) @@ -1206,7 +1200,7 @@ impl AppBundle { let from = app_release.join("app-release.aab"); let to = app_release.join(format!( "{}-{}.aab", - self.build.krate.bundled_app_name(), + self.build.bundled_app_name(), self.build.target )); diff --git a/packages/cli/src/build/mod.rs b/packages/cli/src/build/mod.rs index 59ea259ee6..73856da681 100644 --- a/packages/cli/src/build/mod.rs +++ b/packages/cli/src/build/mod.rs @@ -4,6 +4,13 @@ //! //! Uses a request -> response architecture that allows you to monitor the progress with an optional message //! receiver. +//! +//! +//! Targets +//! - Request +//! - State +//! - Bundle +//! - Handle mod builder; mod bundle; diff --git a/packages/cli/src/build/progress.rs b/packages/cli/src/build/progress.rs index 90ecac80e9..d283323515 100644 --- a/packages/cli/src/build/progress.rs +++ b/packages/cli/src/build/progress.rs @@ -7,7 +7,6 @@ use std::path::PathBuf; pub(crate) type ProgressTx = UnboundedSender; pub(crate) type ProgressRx = UnboundedReceiver; -#[derive(Debug)] #[allow(clippy::large_enum_variant)] pub(crate) enum BuildUpdate { Progress { stage: BuildStage }, diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 5334d530e9..9efc7d6fd3 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1,20 +1,22 @@ use super::{progress::ProgressTx, AndroidTools, BuildArtifacts, PatchData}; -use crate::{dioxus_crate::DioxusCrate, TargetArgs}; use crate::{link::LinkAction, BuildArgs}; use crate::{AppBundle, Platform, Result, TraceSrc}; +use crate::{DioxusConfig, Workspace}; use anyhow::Context; use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV}; use dioxus_cli_opt::AssetManifest; use itertools::Itertools; -use krates::{cm::TargetKind, Utf8PathBuf}; +use krates::{cm::TargetKind, KrateDetails, Krates, NodeId, Utf8PathBuf}; use serde::Deserialize; use std::{ path::{Path, PathBuf}, process::Stdio, + sync::Arc, time::{Instant, SystemTime}, }; use target_lexicon::Triple; use tokio::{io::AsyncBufReadExt, process::Command}; +use toml_edit::Item; use uuid::Uuid; /// This struct is used to plan the build process. @@ -27,10 +29,16 @@ use uuid::Uuid; /// Creating a buildplan also lets us introspect build requests and modularize our build process. /// This will, however, lead to duplicate fields between the CLI and the build engine. This is fine /// since we have the freedom to evolve the schema internally without breaking the API. -#[derive(Clone, Debug)] +/// +/// Since we resolve the build request before initializing the CLI, it also serves as a place to store +/// resolved "serve" arguments, which is why it takes ServeArgs instead of BuildArgs. Simply wrap the +/// BuildArgs in a default ServeArgs and pass it in. +#[derive(Clone)] pub(crate) struct BuildRequest { - /// - pub(crate) krate: DioxusCrate, + pub(crate) workspace: Arc, + pub(crate) crate_package: NodeId, + pub(crate) config: DioxusConfig, + pub(crate) crate_target: Arc, // / pub(crate) fullstack: bool, @@ -82,8 +90,6 @@ pub(crate) struct BuildRequest { pub(crate) debug_symbols: bool, pub(crate) inject_loading_scripts: bool, - - pub(crate) force_sequential: bool, } /// dx can produce different "modes" of a build. A "regular" build is a "base" build. The Fat and Thin @@ -104,6 +110,10 @@ pub enum BuildMode { }, } +pub(crate) static PROFILE_WASM: &str = "wasm-dev"; +pub(crate) static PROFILE_ANDROID: &str = "android-dev"; +pub(crate) static PROFILE_SERVER: &str = "server-dev"; + impl BuildRequest { /// Create a new build request /// @@ -113,95 +123,198 @@ impl BuildRequest { /// When creating a new build request we need to take into account /// - The user's command line arguments /// - The crate's Cargo.toml - /// - The dioxus.tomkl + /// - The dioxus.toml /// - The user's CliSettings /// - The workspace /// - The host (android tools, installed frameworks, etc) /// - The intended platform /// /// We will attempt to autodetect a number of things if not provided. - pub fn new( - args: BuildArgs, - krate: DioxusCrate, - progress: ProgressTx, - mode: BuildMode, - ) -> Result { - let default_platform = krate.default_platform(); - let mut features = vec![]; - let mut no_default_features = false; - - // The user passed --platform XYZ but already has `default = ["ABC"]` in their Cargo.toml - // We want to strip out the default platform and use the one they passed, setting no-default-features - if args.args.platform.is_some() && default_platform.is_some() { - no_default_features = true; - features.extend(krate.platformless_features()); - } + pub async fn new(args: &BuildArgs) -> Result { + let workspace = Workspace::current().await?; + + let package = Self::find_main_package(&workspace.krates, args.package.clone())?; - // Inherit the platform from the args, or auto-detect it - let platform = args.args - .platform - .map(|p| Some(p)) - .unwrap_or_else(|| krate.autodetect_platform().map(|a| a.0)) - .context("No platform was specified and could not be auto-detected. Please specify a platform with `--platform ` or set a default platform using a cargo feature.")?; - - // Add any features required to turn on the client - features.push(krate.feature_for_platform(platform)); - - // Make sure we set the fullstack platform so we actually build the fullstack variant - // Users need to enable "fullstack" in their default feature set. - // todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled - let fullstack = args.fullstack || krate.has_dioxus_feature("fullstack"); - - // Set the profile of the build if it's not already set - // This is mostly used for isolation of builds (preventing thrashing) but also useful to have multiple performance profiles - // We might want to move some of these profiles into dioxus.toml and make them "virtual". - let profile = match args.args.profile { - Some(profile) => profile, - None if args.args.release => "release".to_string(), - None => match platform { - Platform::Android => crate::dioxus_crate::PROFILE_ANDROID.to_string(), - Platform::Web => crate::dioxus_crate::PROFILE_WASM.to_string(), - Platform::Server => crate::dioxus_crate::PROFILE_SERVER.to_string(), - _ => "dev".to_string(), - }, + let dioxus_config = DioxusConfig::load(&workspace.krates, package)?.unwrap_or_default(); + + let target_kind = match args.example.is_some() { + true => TargetKind::Example, + false => TargetKind::Bin, }; - let device = args.device.unwrap_or(false); - - // We want a real triple to build with, so we'll autodetect it if it's not provided - // The triple ends up being a source of truth for us later hence this work to figure it out - let target = match args.args.target { - Some(target) => target, - None => match platform { - // Generally just use the host's triple for native executables unless specified otherwisea - Platform::MacOS - | Platform::Windows - | Platform::Linux - | Platform::Server - | Platform::Liveview => target_lexicon::HOST, - Platform::Web => "wasm32-unknown-unknown".parse().unwrap(), - - // For iOS we should prefer the actual architecture for the simulator, but in lieu of actually - // figuring that out, we'll assume aarch64 on m-series and x86_64 otherwise - Platform::Ios => { - // use the host's architecture and sim if --device is passed - use target_lexicon::{Architecture, HOST}; - match HOST.architecture { - Architecture::Aarch64(_) if device => "aarch64-apple-ios".parse().unwrap(), - Architecture::Aarch64(_) => "aarch64-apple-ios-sim".parse().unwrap(), - _ if device => "x86_64-apple-ios".parse().unwrap(), - _ => "x86_64-apple-ios-sim".parse().unwrap(), - } + let main_package = &workspace.krates[package]; + + let target_name = args + .example + .clone() + .or(args.bin.clone()) + .or_else(|| { + if let Some(default_run) = &main_package.default_run { + return Some(default_run.to_string()); } - // Same idea with android but we figure out the connected device using adb - // for now we use - Platform::Android => { - "aarch64-linux-android".parse().unwrap() - // "unknown-linux-android".parse().unwrap() + let bin_count = main_package + .targets + .iter() + .filter(|x| x.kind.contains(&target_kind)) + .count(); + + if bin_count != 1 { + return None; } - }, - }; + + main_package.targets.iter().find_map(|x| { + if x.kind.contains(&target_kind) { + Some(x.name.clone()) + } else { + None + } + }) + }) + .unwrap_or(workspace.krates[package].name.clone()); + + let target = main_package + .targets + .iter() + .find(|target| { + target_name == target.name.as_str() && target.kind.contains(&target_kind) + }) + .with_context(|| { + let target_of_kind = |kind|-> String { + let filtered_packages = main_package + .targets + .iter() + .filter_map(|target| { + target.kind.contains(kind).then_some(target.name.as_str()) + }).collect::>(); + filtered_packages.join(", ")}; + if let Some(example) = &args.example { + let examples = target_of_kind(&TargetKind::Example); + format!("Failed to find example {example}. \nAvailable examples are:\n{}", examples) + } else if let Some(bin) = &args.bin { + let binaries = target_of_kind(&TargetKind::Bin); + format!("Failed to find binary {bin}. \nAvailable binaries are:\n{}", binaries) + } else { + format!("Failed to find target {target_name}. \nIt looks like you are trying to build dioxus in a library crate. \ + You either need to run dx from inside a binary crate or build a specific example with the `--example` flag. \ + Available examples are:\n{}", target_of_kind(&TargetKind::Example)) + } + })? + .clone(); + + // // Make sure we have a server feature if we're building a fullstack app + // // + // // todo(jon): eventually we want to let users pass a `--server ` flag to specify a package to use as the server + // // however, it'll take some time to support that and we don't have a great RPC binding layer between the two yet + // if self.fullstack && self.server_features.is_empty() { + // return Err(anyhow::anyhow!("Fullstack builds require a server feature on the target crate. Add a `server` feature to the crate and try again.").into()); + // } + + todo!(); + + // let default_platform = krate.default_platform(); + // let mut features = vec![]; + // let mut no_default_features = false; + + // // The user passed --platform XYZ but already has `default = ["ABC"]` in their Cargo.toml + // // We want to strip out the default platform and use the one they passed, setting no-default-features + // if args.platform.is_some() && default_platform.is_some() { + // no_default_features = true; + // features.extend(krate.platformless_features()); + // } + + // // Inherit the platform from the args, or auto-detect it + // let platform = args + // .platform + // .map(|p| Some(p)) + // .unwrap_or_else(|| krate.autodetect_platform().map(|a| a.0)) + // .context("No platform was specified and could not be auto-detected. Please specify a platform with `--platform ` or set a default platform using a cargo feature.")?; + + // // Add any features required to turn on the client + // features.push(krate.feature_for_platform(platform)); + + // // Make sure we set the fullstack platform so we actually build the fullstack variant + // // Users need to enable "fullstack" in their default feature set. + // // todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled + // let fullstack = args.fullstack || krate.has_dioxus_feature("fullstack"); + + // // Set the profile of the build if it's not already set + // // This is mostly used for isolation of builds (preventing thrashing) but also useful to have multiple performance profiles + // // We might want to move some of these profiles into dioxus.toml and make them "virtual". + // let profile = match args.args.profile { + // Some(profile) => profile, + // None if args.args.release => "release".to_string(), + // None => match platform { + // Platform::Android => PROFILE_ANDROID.to_string(), + // Platform::Web => PROFILE_WASM.to_string(), + // Platform::Server => PROFILE_SERVER.to_string(), + // _ => "dev".to_string(), + // }, + // }; + + // let device = args.device.unwrap_or(false); + + // // We want a real triple to build with, so we'll autodetect it if it's not provided + // // The triple ends up being a source of truth for us later hence this work to figure it out + // let target = match args.target { + // Some(target) => target, + // None => match platform { + // // Generally just use the host's triple for native executables unless specified otherwisea + // Platform::MacOS + // | Platform::Windows + // | Platform::Linux + // | Platform::Server + // | Platform::Liveview => target_lexicon::HOST, + // Platform::Web => "wasm32-unknown-unknown".parse().unwrap(), + + // // For iOS we should prefer the actual architecture for the simulator, but in lieu of actually + // // figuring that out, we'll assume aarch64 on m-series and x86_64 otherwise + // Platform::Ios => { + // // use the host's architecture and sim if --device is passed + // use target_lexicon::{Architecture, HOST}; + // match HOST.architecture { + // Architecture::Aarch64(_) if device => "aarch64-apple-ios".parse().unwrap(), + // Architecture::Aarch64(_) => "aarch64-apple-ios-sim".parse().unwrap(), + // _ if device => "x86_64-apple-ios".parse().unwrap(), + // _ => "x86_64-apple-ios-sim".parse().unwrap(), + // } + // } + + // // Same idea with android but we figure out the connected device using adb + // // for now we use + // Platform::Android => { + // "aarch64-linux-android".parse().unwrap() + // // "unknown-linux-android".parse().unwrap() + // } + // }, + // }; + + // // Enable hot reload. + // if self.hot_reload.is_none() { + // self.hot_reload = Some(krate.workspace.settings.always_hot_reload.unwrap_or(true)); + // } + + // // Open browser. + // if self.open.is_none() { + // self.open = Some( + // krate + // .workspace + // .settings + // .always_open_browser + // .unwrap_or_default(), + // ); + // } + + // // Set WSL file poll interval. + // if self.wsl_file_poll_interval.is_none() { + // self.wsl_file_poll_interval = + // Some(krate.workspace.settings.wsl_file_poll_interval.unwrap_or(2)); + // } + + // // Set always-on-top for desktop. + // if self.always_on_top.is_none() { + // self.always_on_top = Some(krate.workspace.settings.always_on_top.unwrap_or(true)) + // } // Determine arch if android @@ -232,65 +345,35 @@ impl BuildRequest { // self.arch = Some(arch); // } - Ok(Self { - progress, - mode, - platform, - features, - no_default_features, - krate, - custom_target_dir: None, - profile, - fullstack, - target, - device, - nightly: args.args.nightly, - package: args.args.package, - release: args.args.release, - skip_assets: args.skip_assets, - ssg: args.ssg, - cranelift: args.cranelift, - cargo_args: args.args.cargo_args, - wasm_split: args.wasm_split, - debug_symbols: args.debug_symbols, - inject_loading_scripts: args.inject_loading_scripts, - force_sequential: args.force_sequential, - }) - } - - /// Run the build command with a pretty loader, returning the executable output location - /// - /// This will also run the fullstack build. Note that fullstack is handled separately within this - /// code flow rather than outside of it. - pub(crate) async fn build_all(self) -> Result { - tracing::debug!( - "Running build command... {}", - if self.force_sequential { - "(sequentially)" - } else { - "" - } - ); - - let (app, server) = match self.force_sequential { - true => futures_util::future::try_join(self.cargo_build(), self.build_server()).await?, - false => (self.cargo_build().await?, self.build_server().await?), - }; - - AppBundle::new(self, app, server).await - } - - pub(crate) async fn build_server(&self) -> Result> { - tracing::debug!("Building server..."); - - if !self.fullstack { - return Ok(None); - } - - let mut cloned = self.clone(); - cloned.platform = Platform::Server; - - Ok(Some(cloned.cargo_build().await?)) + todo!() + // Ok(Self { + // hotreload: todo!(), + // open_browser: todo!(), + // wsl_file_poll_interval: todo!(), + // always_on_top: todo!(), + // progress, + // mode, + // platform, + // features, + // no_default_features, + // krate, + // custom_target_dir: None, + // profile, + // fullstack, + // target, + // device, + // nightly: args.nightly, + // package: args.package, + // release: args.release, + // skip_assets: args.skip_assets, + // ssg: args.ssg, + // cranelift: args.cranelift, + // cargo_args: args.args.cargo_args, + // wasm_split: args.wasm_split, + // debug_symbols: args.debug_symbols, + // inject_loading_scripts: args.inject_loading_scripts, + // force_sequential: args.force_sequential, + // }) } pub(crate) async fn cargo_build(&self) -> Result { @@ -435,7 +518,7 @@ impl BuildRequest { let mut cmd = Command::new(direct_rustc[0].clone()); cmd.args(direct_rustc[1..].iter()); cmd.envs(self.env_vars()?); - cmd.current_dir(self.krate.workspace_dir()); + cmd.current_dir(self.workspace_dir()); cmd.arg(format!( "-Clinker={}", dunce::canonicalize(std::env::current_exe().unwrap()) @@ -449,7 +532,7 @@ impl BuildRequest { // Otherwise build up the command using cargo rustc let mut cmd = Command::new("cargo"); cmd.arg("rustc") - .current_dir(self.krate.crate_dir()) + .current_dir(self.crate_dir()) .arg("--message-format") .arg("json-diagnostic-rendered-ansi") .args(self.build_arguments()) @@ -487,14 +570,14 @@ impl BuildRequest { cargo_args.push(package.clone()); } - match self.krate.executable_type() { + match self.executable_type() { krates::cm::TargetKind::Bin => cargo_args.push("--bin".to_string()), krates::cm::TargetKind::Lib => cargo_args.push("--lib".to_string()), krates::cm::TargetKind::Example => cargo_args.push("--example".to_string()), _ => {} }; - cargo_args.push(self.krate.executable_name().to_string()); + cargo_args.push(self.executable_name().to_string()); cargo_args.extend(self.cargo_args.clone()); @@ -590,8 +673,7 @@ impl BuildRequest { if !self.no_default_features { features.extend( - self.krate - .package() + self.package() .features .get("default") .cloned() @@ -645,13 +727,13 @@ impl BuildRequest { // Otherwise, use cargo metadata let units = self - .krate .workspace .krates .krates_filtered(krates::DepKind::Dev) .iter() .map(|k| k.targets.len()) .sum::(); + (units as f64 / 3.5) as usize } @@ -726,10 +808,10 @@ impl BuildRequest { // If this is a release build, bake the base path and title // into the binary with env vars if self.release { - if let Some(base_path) = &self.krate.config.web.app.base_path { + if let Some(base_path) = &self.config.web.app.base_path { env_vars.push((ASSET_ROOT_ENV, base_path.clone())); } - env_vars.push((APP_TITLE_ENV, self.krate.config.web.app.title.clone())); + env_vars.push((APP_TITLE_ENV, self.config.web.app.title.clone())); } Ok(env_vars) @@ -887,14 +969,14 @@ impl BuildRequest { /// Get the path to the wasm bindgen javascript output file pub fn wasm_bindgen_js_output_file(&self) -> PathBuf { self.wasm_bindgen_out_dir() - .join(self.krate.executable_name()) + .join(self.executable_name()) .with_extension("js") } /// Get the path to the wasm bindgen wasm output file pub fn wasm_bindgen_wasm_output_file(&self) -> PathBuf { self.wasm_bindgen_out_dir() - .join(format!("{}_bg", self.krate.executable_name())) + .join(format!("{}_bg", self.executable_name())) .with_extension("wasm") } @@ -919,8 +1001,8 @@ impl BuildRequest { Platform::Server => platform_dir.clone(), // ends up *next* to the public folder // These might not actually need to be called `.app` but it does let us run these with `open` - Platform::MacOS => platform_dir.join(format!("{}.app", self.krate.bundled_app_name())), - Platform::Ios => platform_dir.join(format!("{}.app", self.krate.bundled_app_name())), + Platform::MacOS => platform_dir.join(format!("{}.app", self.bundled_app_name())), + Platform::Ios => platform_dir.join(format!("{}.app", self.bundled_app_name())), // in theory, these all could end up directly in the root dir Platform::Android => platform_dir.join("app"), // .apk (after bundling) @@ -931,7 +1013,7 @@ impl BuildRequest { } pub(crate) fn platform_dir(&self) -> PathBuf { - self.krate.build_dir(self.platform, self.release) + self.build_dir(self.platform, self.release) } pub fn asset_dir(&self) -> PathBuf { @@ -966,11 +1048,11 @@ impl BuildRequest { pub fn platform_exe_name(&self) -> String { match self.platform { - Platform::MacOS => self.krate.executable_name().to_string(), - Platform::Ios => self.krate.executable_name().to_string(), - Platform::Server => self.krate.executable_name().to_string(), - Platform::Liveview => self.krate.executable_name().to_string(), - Platform::Windows => format!("{}.exe", self.krate.executable_name()), + Platform::MacOS => self.executable_name().to_string(), + Platform::Ios => self.executable_name().to_string(), + Platform::Server => self.executable_name().to_string(), + Platform::Liveview => self.executable_name().to_string(), + Platform::Windows => format!("{}.exe", self.executable_name()), // from the apk spec, the root exe is a shared library // we include the user's rust code as a shared library with a fixed namespacea @@ -979,7 +1061,7 @@ impl BuildRequest { Platform::Web => unimplemented!("there's no main exe on web"), // this will be wrong, I think, but not important? // todo: maybe this should be called AppRun? - Platform::Linux => self.krate.executable_name().to_string(), + Platform::Linux => self.executable_name().to_string(), } } @@ -1019,8 +1101,8 @@ impl BuildRequest { app_name: String, } let hbs_data = HbsTypes { - application_id: self.krate.full_mobile_app_name(), - app_name: self.krate.bundled_app_name(), + application_id: self.full_mobile_app_name(), + app_name: self.bundled_app_name(), }; let hbs = handlebars::Handlebars::new(); @@ -1186,6 +1268,637 @@ impl BuildRequest { pub(crate) fn is_patch(&self) -> bool { matches!(&self.mode, BuildMode::Thin { .. }) } + + // pub(crate) async fn new(args: &TargetArgs) -> Result { + + // Ok(Self { + // workspace: workspace.clone(), + // package, + // config: dioxus_config, + // target: Arc::new(target), + // }) + // } + + /// The asset dir we used to support before manganis became the default. + /// This generally was just a folder in your Dioxus.toml called "assets" or "public" where users + /// would store their assets. + /// + /// With manganis you now use `asset!()` and we pick it up automatically. + pub(crate) fn legacy_asset_dir(&self) -> Option { + self.config + .application + .asset_dir + .clone() + .map(|dir| self.crate_dir().join(dir)) + } + + /// Get the list of files in the "legacy" asset directory + pub(crate) fn legacy_asset_dir_files(&self) -> Vec { + let mut files = vec![]; + + let Some(legacy_asset_dir) = self.legacy_asset_dir() else { + return files; + }; + + let Ok(read_dir) = legacy_asset_dir.read_dir() else { + return files; + }; + + for entry in read_dir.flatten() { + files.push(entry.path()); + } + + files + } + + /// Get the directory where this app can write to for this session that's guaranteed to be stable + /// for the same app. This is useful for emitting state like window position and size. + /// + /// The directory is specific for this app and might be + pub(crate) fn session_cache_dir(&self) -> PathBuf { + self.internal_out_dir() + .join(self.executable_name()) + .join("session-cache") + } + + /// Get the outdir specified by the Dioxus.toml, relative to the crate directory. + /// We don't support workspaces yet since that would cause a collision of bundles per project. + pub(crate) fn crate_out_dir(&self) -> Option { + self.config + .application + .out_dir + .as_ref() + .map(|out_dir| self.crate_dir().join(out_dir)) + } + + /// Compose an out directory. Represents the typical "dist" directory that + /// is "distributed" after building an application (configurable in the + /// `Dioxus.toml`). + fn internal_out_dir(&self) -> PathBuf { + let dir = self.workspace_dir().join("target").join("dx"); + std::fs::create_dir_all(&dir).unwrap(); + dir + } + + /// Create a workdir for the given platform + /// This can be used as a temporary directory for the build, but in an observable way such that + /// you can see the files in the directory via `target` + /// + /// target/dx/build/app/web/ + /// target/dx/build/app/web/public/ + /// target/dx/build/app/web/server.exe + pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf { + self.internal_out_dir() + .join(self.executable_name()) + .join(if release { "release" } else { "debug" }) + .join(platform.build_folder_name()) + } + + /// target/dx/bundle/app/ + /// target/dx/bundle/app/blah.app + /// target/dx/bundle/app/blah.exe + /// target/dx/bundle/app/public/ + pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf { + self.internal_out_dir() + .join(self.executable_name()) + .join("bundle") + .join(platform.build_folder_name()) + } + + /// Get the workspace directory for the crate + pub(crate) fn workspace_dir(&self) -> PathBuf { + self.workspace + .krates + .workspace_root() + .as_std_path() + .to_path_buf() + } + + /// Get the directory of the crate + pub(crate) fn crate_dir(&self) -> PathBuf { + self.package() + .manifest_path + .parent() + .unwrap() + .as_std_path() + .to_path_buf() + } + + /// Get the main source file of the target + pub(crate) fn main_source_file(&self) -> PathBuf { + self.crate_target.src_path.as_std_path().to_path_buf() + } + + /// Get the package we are currently in + pub(crate) fn package(&self) -> &krates::cm::Package { + &self.workspace.krates[self.crate_package] + } + + /// Get the name of the package we are compiling + pub(crate) fn executable_name(&self) -> &str { + &self.crate_target.name + } + + /// Get the type of executable we are compiling + pub(crate) fn executable_type(&self) -> krates::cm::TargetKind { + self.crate_target.kind[0].clone() + } + + /// Try to autodetect the platform from the package by reading its features + /// + /// Read the default-features list and/or the features list on dioxus to see if we can autodetect the platform + pub(crate) fn autodetect_platform(&self) -> Option<(Platform, String)> { + let krate = self.workspace.krates.krates_by_name("dioxus").next()?; + + // We're going to accumulate the platforms that are enabled + // This will let us create a better warning if multiple platforms are enabled + let manually_enabled_platforms = self + .workspace + .krates + .get_enabled_features(krate.kid)? + .iter() + .flat_map(|feature| { + tracing::trace!("Autodetecting platform from feature {feature}"); + Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string())) + }) + .collect::>(); + + if manually_enabled_platforms.len() > 1 { + tracing::error!("Multiple platforms are enabled. Please specify a platform with `--platform ` or set a single default platform using a cargo feature."); + for platform in manually_enabled_platforms { + tracing::error!(" - {platform:?}"); + } + return None; + } + + if manually_enabled_platforms.len() == 1 { + return manually_enabled_platforms.first().cloned(); + } + + // Let's try and find the list of platforms from the feature list + // This lets apps that specify web + server to work without specifying the platform. + // This is because we treat `server` as a binary thing rather than a dedicated platform, so at least we can disambiguate it + let possible_platforms = self + .package() + .features + .iter() + .filter_map(|(feature, _features)| { + match Platform::autodetect_from_cargo_feature(feature) { + Some(platform) => Some((platform, feature.to_string())), + None => { + let auto_implicit = _features + .iter() + .filter_map(|f| { + if !f.starts_with("dioxus?/") && !f.starts_with("dioxus/") { + return None; + } + + let rest = f + .trim_start_matches("dioxus/") + .trim_start_matches("dioxus?/"); + + Platform::autodetect_from_cargo_feature(rest) + }) + .collect::>(); + + if auto_implicit.len() == 1 { + Some((auto_implicit.first().copied().unwrap(), feature.to_string())) + } else { + None + } + } + } + }) + .filter(|platform| platform.0 != Platform::Server) + .collect::>(); + + if possible_platforms.len() == 1 { + return possible_platforms.first().cloned(); + } + + None + } + + /// Check if dioxus is being built with a particular feature + pub(crate) fn has_dioxus_feature(&self, filter: &str) -> bool { + self.workspace + .krates + .krates_by_name("dioxus") + .any(|dioxus| { + self.workspace + .krates + .get_enabled_features(dioxus.kid) + .map(|features| features.contains(filter)) + .unwrap_or_default() + }) + } + + /// Get the features required to build for the given platform + pub(crate) fn feature_for_platform(&self, platform: Platform) -> String { + let package = self.package(); + + // Try to find the feature that activates the dioxus feature for the given platform + let dioxus_feature = platform.feature_name(); + + let res = package.features.iter().find_map(|(key, features)| { + // if the feature is just the name of the platform, we use that + if key == dioxus_feature { + return Some(key.clone()); + } + + // Otherwise look for the feature that starts with dioxus/ or dioxus?/ and matches the platform + for feature in features { + if let Some((_, after_dioxus)) = feature.split_once("dioxus") { + if let Some(dioxus_feature_enabled) = + after_dioxus.trim_start_matches('?').strip_prefix('/') + { + // If that enables the feature we are looking for, return that feature + if dioxus_feature_enabled == dioxus_feature { + return Some(key.clone()); + } + } + } + } + + None + }); + + res.unwrap_or_else(|| { + let fallback = format!("dioxus/{}", platform.feature_name()) ; + tracing::debug!( + "Could not find explicit feature for platform {platform}, passing `fallback` instead" + ); + fallback + }) + } + + /// Check if assets should be pre_compressed. This will only be true in release mode if the user + /// has enabled pre_compress in the web config. + pub(crate) fn should_pre_compress_web_assets(&self, release: bool) -> bool { + self.config.web.pre_compress && release + } + + // The `opt-level=1` increases build times, but can noticeably decrease time + // between saving changes and being able to interact with an app (for wasm/web). The "overall" + // time difference (between having and not having the optimization) can be + // almost imperceptible (~1 s) but also can be very noticeable (~6 s) — depends + // on setup (hardware, OS, browser, idle load). + // + // Find or create the client and server profiles in the top-level Cargo.toml file + // todo(jon): we should/could make these optional by placing some defaults somewhere + pub(crate) fn initialize_profiles(&self) -> crate::Result<()> { + let config_path = self.workspace_dir().join("Cargo.toml"); + let mut config = match std::fs::read_to_string(&config_path) { + Ok(config) => config.parse::().map_err(|e| { + crate::Error::Other(anyhow::anyhow!("Failed to parse Cargo.toml: {}", e)) + })?, + Err(_) => Default::default(), + }; + + if let Item::Table(table) = config + .as_table_mut() + .entry("profile") + .or_insert(Item::Table(Default::default())) + { + if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_WASM) { + let mut client = toml_edit::Table::new(); + client.insert("inherits", Item::Value("dev".into())); + client.insert("opt-level", Item::Value(1.into())); + entry.insert(Item::Table(client)); + } + + if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_SERVER) { + let mut server = toml_edit::Table::new(); + server.insert("inherits", Item::Value("dev".into())); + entry.insert(Item::Table(server)); + } + + if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_ANDROID) { + let mut android = toml_edit::Table::new(); + android.insert("inherits", Item::Value("dev".into())); + entry.insert(Item::Table(android)); + } + } + + std::fs::write(config_path, config.to_string()) + .context("Failed to write profiles to Cargo.toml")?; + + Ok(()) + } + + fn default_ignore_list(&self) -> Vec<&'static str> { + vec![ + ".git", + ".github", + ".vscode", + "target", + "node_modules", + "dist", + "*~", + ".*", + "*.lock", + "*.log", + ] + } + + /// Create a new gitignore map for this target crate + /// + /// todo(jon): this is a bit expensive to build, so maybe we should cache it? + pub fn workspace_gitignore(&self) -> ignore::gitignore::Gitignore { + let crate_dir = self.crate_dir(); + + let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(&crate_dir); + ignore_builder.add(crate_dir.join(".gitignore")); + + let workspace_dir = self.workspace_dir(); + ignore_builder.add(workspace_dir.join(".gitignore")); + + for path in self.default_ignore_list() { + ignore_builder + .add_line(None, path) + .expect("failed to add path to file excluded"); + } + + ignore_builder.build().unwrap() + } + + /// Return the version of the wasm-bindgen crate if it exists + pub fn wasm_bindgen_version(&self) -> Option { + self.workspace + .krates + .krates_by_name("wasm-bindgen") + .next() + .map(|krate| krate.krate.version.to_string()) + } + + pub(crate) fn default_platform(&self) -> Option { + let default = self.package().features.get("default")?; + + // we only trace features 1 level deep.. + for feature in default.iter() { + // If the user directly specified a platform we can just use that. + if feature.starts_with("dioxus/") { + let dx_feature = feature.trim_start_matches("dioxus/"); + let auto = Platform::autodetect_from_cargo_feature(dx_feature); + if auto.is_some() { + return auto; + } + } + + // If the user is specifying an internal feature that points to a platform, we can use that + let internal_feature = self.package().features.get(feature); + if let Some(internal_feature) = internal_feature { + for feature in internal_feature { + if feature.starts_with("dioxus/") { + let dx_feature = feature.trim_start_matches("dioxus/"); + let auto = Platform::autodetect_from_cargo_feature(dx_feature); + if auto.is_some() { + return auto; + } + } + } + } + } + + None + } + + /// Gather the features that are enabled for the package + pub(crate) fn platformless_features(&self) -> Vec { + let default = self.package().features.get("default").unwrap(); + let mut kept_features = vec![]; + + // Only keep the top-level features in the default list that don't point to a platform directly + // IE we want to drop `web` if default = ["web"] + 'top: for feature in default { + // Don't keep features that point to a platform via dioxus/blah + if feature.starts_with("dioxus/") { + let dx_feature = feature.trim_start_matches("dioxus/"); + if Platform::autodetect_from_cargo_feature(dx_feature).is_some() { + continue 'top; + } + } + + // Don't keep features that point to a platform via an internal feature + if let Some(internal_feature) = self.package().features.get(feature) { + for feature in internal_feature { + if feature.starts_with("dioxus/") { + let dx_feature = feature.trim_start_matches("dioxus/"); + if Platform::autodetect_from_cargo_feature(dx_feature).is_some() { + continue 'top; + } + } + } + } + + // Otherwise we can keep it + kept_features.push(feature.to_string()); + } + + kept_features + } + + /// Return the list of paths that we should watch for changes. + pub(crate) fn watch_paths(&self) -> Vec { + let mut watched_paths = vec![]; + + // Get a list of *all* the crates with Rust code that we need to watch. + // This will end up being dependencies in the workspace and non-workspace dependencies on the user's computer. + let mut watched_crates = self.local_dependencies(); + watched_crates.push(self.crate_dir()); + + // Now, watch all the folders in the crates, but respecting their respective ignore files + for krate_root in watched_crates { + // Build the ignore builder for this crate, but with our default ignore list as well + let ignore = self.ignore_for_krate(&krate_root); + + for entry in krate_root.read_dir().unwrap() { + let Ok(entry) = entry else { + continue; + }; + + if ignore + .matched(entry.path(), entry.path().is_dir()) + .is_ignore() + { + continue; + } + + watched_paths.push(entry.path().to_path_buf()); + } + } + + watched_paths.dedup(); + + watched_paths + } + + fn ignore_for_krate(&self, path: &Path) -> ignore::gitignore::Gitignore { + let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(path); + for path in self.default_ignore_list() { + ignore_builder + .add_line(None, path) + .expect("failed to add path to file excluded"); + } + ignore_builder.build().unwrap() + } + + /// Get all the Manifest paths for dependencies that we should watch. Will not return anything + /// in the `.cargo` folder - only local dependencies will be watched. + /// + /// This returns a list of manifest paths + /// + /// Extend the watch path to include: + /// + /// - the assets directory - this is so we can hotreload CSS and other assets by default + /// - the Cargo.toml file - this is so we can hotreload the project if the user changes dependencies + /// - the Dioxus.toml file - this is so we can hotreload the project if the user changes the Dioxus config + pub(crate) fn local_dependencies(&self) -> Vec { + let mut paths = vec![]; + + for (dependency, _edge) in self.workspace.krates.get_deps(self.crate_package) { + let krate = match dependency { + krates::Node::Krate { krate, .. } => krate, + krates::Node::Feature { krate_index, .. } => { + &self.workspace.krates[krate_index.index()] + } + }; + + if krate + .manifest_path + .components() + .any(|c| c.as_str() == ".cargo") + { + continue; + } + + paths.push( + krate + .manifest_path + .parent() + .unwrap() + .to_path_buf() + .into_std_path_buf(), + ); + } + + paths + } + + pub(crate) fn all_watched_crates(&self) -> Vec { + let mut krates: Vec = self + .local_dependencies() + .into_iter() + .map(|p| { + p.parent() + .expect("Local manifest to exist and have a parent") + .to_path_buf() + }) + .chain(Some(self.crate_dir())) + .collect(); + + krates.dedup(); + + krates + } + + pub(crate) fn mobile_org(&self) -> String { + let identifier = self.bundle_identifier(); + let mut split = identifier.splitn(3, '.'); + let sub = split + .next() + .expect("Identifier to have at least 3 periods like `com.example.app`"); + let tld = split + .next() + .expect("Identifier to have at least 3 periods like `com.example.app`"); + format!("{}.{}", sub, tld) + } + + pub(crate) fn bundled_app_name(&self) -> String { + use convert_case::{Case, Casing}; + self.executable_name().to_case(Case::Pascal) + } + + pub(crate) fn full_mobile_app_name(&self) -> String { + format!("{}.{}", self.mobile_org(), self.bundled_app_name()) + } + + pub(crate) fn bundle_identifier(&self) -> String { + if let Some(identifier) = self.config.bundle.identifier.clone() { + return identifier.clone(); + } + + format!("com.example.{}", self.bundled_app_name()) + } + + /// Find the main package in the workspace + fn find_main_package(krates: &Krates, package: Option) -> Result { + if let Some(package) = package { + let mut workspace_members = krates.workspace_members(); + let found = workspace_members.find_map(|node| { + if let krates::Node::Krate { id, krate, .. } = node { + if krate.name == package { + return Some(id); + } + } + None + }); + + if found.is_none() { + tracing::error!("Could not find package {package} in the workspace. Did you forget to add it to the workspace?"); + tracing::error!("Packages in the workspace:"); + for package in krates.workspace_members() { + if let krates::Node::Krate { krate, .. } = package { + tracing::error!("{}", krate.name()); + } + } + } + + let kid = found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?; + + return Ok(krates.nid_for_kid(kid).unwrap()); + }; + + // Otherwise find the package that is the closest parent of the current directory + let current_dir = std::env::current_dir()?; + let current_dir = current_dir.as_path(); + + // Go through each member and find the path that is a parent of the current directory + let mut closest_parent = None; + for member in krates.workspace_members() { + if let krates::Node::Krate { id, krate, .. } = member { + let member_path = krate.manifest_path.parent().unwrap(); + if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) { + let len = path.components().count(); + match closest_parent { + Some((_, closest_parent_len)) => { + if len < closest_parent_len { + closest_parent = Some((id, len)); + } + } + None => { + closest_parent = Some((id, len)); + } + } + } + } + } + + let kid = closest_parent + .map(|(id, _)| id) + .with_context(|| { + let bin_targets = krates.workspace_members().filter_map(|krate|match krate { + krates::Node::Krate { krate, .. } if krate.targets.iter().any(|t| t.kind.contains(&krates::cm::TargetKind::Bin))=> { + Some(format!("- {}", krate.name)) + } + _ => None + }).collect::>(); + format!("Failed to find binary package to build.\nYou need to either run dx from inside a binary crate or specify a binary package to build with the `--package` flag. Try building again with one of the binary packages in the workspace:\n{}", bin_targets.join("\n")) + })?; + + let package = krates.nid_for_kid(kid).unwrap(); + Ok(package) + } } // pub(crate) fn triple(&self) -> Triple { diff --git a/packages/cli/src/build/verify.rs b/packages/cli/src/build/verify.rs index 2653a2bd2d..bebf55eb34 100644 --- a/packages/cli/src/build/verify.rs +++ b/packages/cli/src/build/verify.rs @@ -10,7 +10,7 @@ impl BuildRequest { tracing::debug!("Verifying tooling..."); self.status_installing_tooling(); - self.krate + self .initialize_profiles() .context("Failed to initialize profiles - dioxus can't build without them. You might need to initialize them yourself.")?; @@ -58,7 +58,7 @@ impl BuildRequest { } // Wasm bindgen - let krate_bindgen_version = self.krate.wasm_bindgen_version().ok_or(anyhow!( + let krate_bindgen_version = self.wasm_bindgen_version().ok_or(anyhow!( "failed to detect wasm-bindgen version, unable to proceed" ))?; diff --git a/packages/cli/src/build/web.rs b/packages/cli/src/build/web.rs index 83de2da433..320c2b0864 100644 --- a/packages/cli/src/build/web.rs +++ b/packages/cli/src/build/web.rs @@ -12,7 +12,7 @@ const DEFAULT_HTML: &str = include_str!("../../assets/web/dev.index.html"); impl AppBundle { pub(crate) fn prepare_html(&self) -> Result { let mut html = { - let crate_root: &Path = &self.build.krate.crate_dir(); + let crate_root: &Path = &self.build.crate_dir(); let custom_html_file = crate_root.join("index.html"); std::fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(DEFAULT_HTML)) }; @@ -26,7 +26,7 @@ impl AppBundle { // Replace any special placeholders in the HTML with resolved values self.replace_template_placeholders(&mut html); - let title = self.build.krate.config.web.app.title.clone(); + let title = self.build.config.web.app.title.clone(); replace_or_insert_before("{app_title}", " Result<()> { // Collect all resources into a list of styles and scripts - let resources = &self.build.krate.config.web.resource; + let resources = &self.build.config.web.resource; let mut style_list = resources.style.clone().unwrap_or_default(); let mut script_list = resources.script.clone().unwrap_or_default(); @@ -71,7 +71,7 @@ impl AppBundle { // Add the base path to the head if this is a debug build if self.is_dev_build() { - if let Some(base_path) = &self.build.krate.config.web.app.base_path { + if let Some(base_path) = &self.build.config.web.app.base_path { head_resources.push_str(&format_base_path_meta_element(base_path)); } } @@ -160,10 +160,10 @@ r#" - -
diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 1a7b157def..d0b81c0e27 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -383,14 +383,6 @@ impl AppBuilder { // These need to be stable within a release version (ie 0.6.0) let mut envs = vec![ (dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()), - ( - dioxus_cli_config::ALWAYS_ON_TOP_ENV, - always_on_top.to_string(), - ), - ( - dioxus_cli_config::APP_TITLE_ENV, - krate.config.web.app.title.clone(), - ), ( dioxus_cli_config::DEVSERVER_IP_ENV, devserver_ip.ip().to_string(), @@ -473,10 +465,10 @@ impl AppBuilder { // Soft-kill the process by sending a sigkill, allowing the process to clean up self.soft_kill().await; - // Wipe out the entropy executables if they exist - if let Some(entropy_app_exe) = self.entropy_app_exe.take() { - _ = std::fs::remove_file(entropy_app_exe); - } + // // Wipe out the entropy executables if they exist + // if let Some(entropy_app_exe) = self.entropy_app_exe.take() { + // _ = std::fs::remove_file(entropy_app_exe); + // } } /// Kill the app and server exes From 429277fa648633a4caac5f2401696d2a38dc6a3b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 11 Apr 2025 20:47:44 -0700 Subject: [PATCH 108/301] revert name change --- packages/cli/src/build/builder.rs | 36 +++++++++++++++---------------- packages/cli/src/build/context.rs | 30 +++++++++++++------------- packages/cli/src/serve/mod.rs | 16 +++++++------- packages/cli/src/serve/output.rs | 10 ++++----- packages/cli/src/serve/server.rs | 18 ++++++++-------- packages/cli/src/serve/update.rs | 4 ++-- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index d0b81c0e27..def8e75827 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1,5 +1,5 @@ use crate::{ - BuildArgs, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, ProgressRx, + BuildArgs, BuildArtifacts, BuildRequest, BuildStage, BuildUpdate, Platform, ProgressRx, ProgressTx, Result, StructuredOutput, }; use anyhow::Context; @@ -153,9 +153,9 @@ impl AppBuilder { } /// 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 { + pub(crate) async fn wait(&mut self) -> BuildUpdate { use futures_util::StreamExt; - use BuilderUpdate::*; + use BuildUpdate::*; // Wait for the build to finish or for it to emit a status message let update = tokio::select! { @@ -164,9 +164,9 @@ impl AppBuilder { // Replace the build with an infinitely pending task so we can select it again without worrying about deadlocks/spins self.build_task = tokio::task::spawn(std::future::pending()); 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)) }, + Ok(Ok(bundle)) => BuildUpdate::BuildReady { bundle }, + Ok(Err(err)) => BuildUpdate::BuildFailed { err }, + Err(err) => BuildUpdate::BuildFailed { err: crate::Error::Runtime(format!("Build panicked! {:?}", err)) }, } }, Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => { @@ -191,7 +191,7 @@ impl AppBuilder { // doing so will cause the changes to be lost since this wait call is called under a cancellable task // todo - move this handling to a separate function that won't be cancelled match &update { - BuilderUpdate::Progress { stage } => { + BuildUpdate::Progress { stage } => { // Prevent updates from flowing in after the build has already finished if !self.is_finished() { self.stage = stage.clone(); @@ -241,8 +241,8 @@ impl AppBuilder { } } } - BuilderUpdate::CompilerMessage { .. } => {} - BuilderUpdate::BuildReady { .. } => { + BuildUpdate::CompilerMessage { .. } => {} + BuildUpdate::BuildReady { .. } => { self.compiled_crates = self.expected_crates; self.bundling_progress = 1.0; self.stage = BuildStage::Success; @@ -250,7 +250,7 @@ impl AppBuilder { self.complete_compile(); self.bundle_end = Some(Instant::now()); } - BuilderUpdate::BuildFailed { .. } => { + BuildUpdate::BuildFailed { .. } => { tracing::debug!("Setting builder to failed state"); self.stage = BuildStage::Failed; } @@ -318,7 +318,7 @@ impl AppBuilder { pub(crate) async fn finish_build(&mut self) -> Result { loop { match self.wait().await { - BuilderUpdate::Progress { stage } => { + BuildUpdate::Progress { stage } => { match &stage { BuildStage::Compiling { current, @@ -342,19 +342,19 @@ impl AppBuilder { tracing::info!(json = ?StructuredOutput::BuildUpdate { stage: stage.clone() }); } - BuilderUpdate::CompilerMessage { message } => { + BuildUpdate::CompilerMessage { message } => { tracing::info!(json = ?StructuredOutput::CargoOutput { message: message.clone() }, %message); } - BuilderUpdate::BuildReady { bundle } => { + BuildUpdate::BuildReady { bundle } => { tracing::debug!(json = ?StructuredOutput::BuildFinished { path: self.build.root_dir(), }); return Ok(bundle); } - BuilderUpdate::BuildFailed { err } => { + BuildUpdate::BuildFailed { err } => { // Flush remaining compiler messages while let Ok(Some(msg)) = self.rx.try_next() { - if let BuilderUpdate::CompilerMessage { message } = msg { + if let BuildUpdate::CompilerMessage { message } = msg { tracing::info!(json = ?StructuredOutput::CargoOutput { message: message.clone() }, %message); } } @@ -362,9 +362,9 @@ impl AppBuilder { tracing::error!(?err, json = ?StructuredOutput::Error { message: err.to_string() }); return Err(err); } - BuilderUpdate::StdoutReceived { msg } => {} - BuilderUpdate::StderrReceived { msg } => {} - BuilderUpdate::ProcessExited { status } => {} + BuildUpdate::StdoutReceived { msg } => {} + BuildUpdate::StderrReceived { msg } => {} + BuildUpdate::ProcessExited { status } => {} } } } diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index 901c457f15..11aa03ae96 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -15,14 +15,14 @@ pub struct BuildContext { pub mode: BuildMode, } -pub(crate) type ProgressTx = UnboundedSender; -pub(crate) type ProgressRx = UnboundedReceiver; +pub(crate) type ProgressTx = UnboundedSender; +pub(crate) type ProgressRx = UnboundedReceiver; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuildId(pub usize); #[allow(clippy::large_enum_variant)] -pub(crate) enum BuilderUpdate { +pub(crate) enum BuildUpdate { Progress { stage: BuildStage, }, @@ -62,26 +62,26 @@ pub(crate) enum BuilderUpdate { impl BuildContext { pub(crate) fn status_wasm_bindgen_start(&self) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::RunningBindgen {}, }); } pub(crate) fn status_splitting_bundle(&self) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::SplittingBundle, }); } pub(crate) fn status_start_bundle(&self) { tracing::debug!("Assembling app bundle"); - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::Bundling {}, }); } pub(crate) fn status_running_gradle(&self) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::RunningGradle, }) } @@ -89,7 +89,7 @@ impl BuildContext { pub(crate) fn status_build_diagnostic(&self, message: CompilerMessage) { _ = self .tx - .unbounded_send(BuilderUpdate::CompilerMessage { message }); + .unbounded_send(BuildUpdate::CompilerMessage { message }); } pub(crate) fn status_build_error(&self, line: String) { @@ -101,7 +101,7 @@ impl BuildContext { } pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::Compiling { current: count, total, @@ -111,7 +111,7 @@ impl BuildContext { } pub(crate) fn status_starting_build(&self, crate_count: usize) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::Starting { patch: matches!(self.mode, BuildMode::Thin { .. }), crate_count, @@ -120,12 +120,12 @@ impl BuildContext { } pub(crate) fn status_copied_asset( - progress: &UnboundedSender, + progress: &UnboundedSender, current: usize, total: usize, path: PathBuf, ) { - _ = progress.unbounded_send(BuilderUpdate::Progress { + _ = progress.unbounded_send(BuildUpdate::Progress { stage: BuildStage::CopyingAssets { current, total, @@ -135,19 +135,19 @@ impl BuildContext { } pub(crate) fn status_optimizing_wasm(&self) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::OptimizingWasm {}, }); } pub(crate) fn status_installing_tooling(&self) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::InstallingTooling {}, }); } pub(crate) fn status_compressing_assets(&self) { - _ = self.tx.unbounded_send(BuilderUpdate::Progress { + _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::CompressingAssets, }); } diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 6dfe4e6165..3089ea2095 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -1,5 +1,5 @@ use crate::{ - AppBuilder, BuildMode, BuildRequest, BuilderUpdate, Error, Platform, Result, ServeArgs, + AppBuilder, BuildMode, BuildRequest, BuildUpdate, Error, Platform, Result, ServeArgs, TraceController, TraceSrc, }; @@ -126,14 +126,14 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { // todo: there might be more things to do here that require coordination with other pieces of the CLI // todo: maybe we want to shuffle the runner around to send an "open" command instead of doing that match update { - BuilderUpdate::Progress { .. } => {} - BuilderUpdate::CompilerMessage { message } => { + BuildUpdate::Progress { .. } => {} + BuildUpdate::CompilerMessage { message } => { screen.push_cargo_log(message); } - BuilderUpdate::BuildFailed { err } => { + BuildUpdate::BuildFailed { err } => { tracing::error!("Build failed: {:?}", err); } - BuilderUpdate::BuildReady { bundle } => { + BuildUpdate::BuildReady { bundle } => { match bundle.mode { BuildMode::Thin { .. } => { // We need to patch the app with the new bundle @@ -160,13 +160,13 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { } } } - BuilderUpdate::StdoutReceived { msg } => { + BuildUpdate::StdoutReceived { msg } => { screen.push_stdio(platform, msg, tracing::Level::INFO); } - BuilderUpdate::StderrReceived { msg } => { + BuildUpdate::StderrReceived { msg } => { screen.push_stdio(platform, msg, tracing::Level::ERROR); } - BuilderUpdate::ProcessExited { status } => { + BuildUpdate::ProcessExited { status } => { if status.success() { tracing::info!( r#"Application [{platform}] exited gracefully. diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 51317f6aa1..b865e2c1aa 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -1,6 +1,6 @@ use crate::{ serve::{ansi_buffer::AnsiStringLine, ServeUpdate, WebServer}, - BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc, + BuildStage, BuildUpdate, Platform, TraceContent, TraceMsg, TraceSrc, }; use crossterm::{ cursor::{Hide, Show}, @@ -370,13 +370,13 @@ impl Output { /// approach, but then we'd need to do that *everywhere* instead of simply performing a react-like /// re-render when external state changes. Ratatui will diff the intermediate buffer, so we at least /// we won't be drawing it. - pub(crate) fn new_build_update(&mut self, update: &BuilderUpdate) { + pub(crate) fn new_build_update(&mut self, update: &BuildUpdate) { match update { - BuilderUpdate::Progress { + BuildUpdate::Progress { stage: BuildStage::Starting { .. }, } => self.tick_animation = true, - BuilderUpdate::BuildReady { .. } => self.tick_animation = false, - BuilderUpdate::BuildFailed { .. } => self.tick_animation = false, + BuildUpdate::BuildReady { .. } => self.tick_animation = false, + BuildUpdate::BuildFailed { .. } => self.tick_animation = false, _ => {} } } diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 57b577557e..6e283a70b4 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -1,7 +1,7 @@ use crate::{ config::WebHttpsConfig, serve::{ServeArgs, ServeUpdate}, - BuildRequest, BuildStage, BuilderUpdate, Platform, Result, TraceSrc, + BuildRequest, BuildStage, BuildUpdate, Platform, Result, TraceSrc, }; use anyhow::Context; use axum::{ @@ -224,9 +224,9 @@ impl WebServer { } /// Sends an updated build status to all clients. - pub(crate) async fn new_build_update(&mut self, update: &BuilderUpdate) { + pub(crate) async fn new_build_update(&mut self, update: &BuildUpdate) { match update { - BuilderUpdate::Progress { stage } => { + BuildUpdate::Progress { stage } => { // Todo(miles): wire up more messages into the splash screen UI match stage { BuildStage::Success => {} @@ -254,9 +254,9 @@ impl WebServer { _ => {} } } - BuilderUpdate::CompilerMessage { .. } => {} - BuilderUpdate::BuildReady { .. } => {} - BuilderUpdate::BuildFailed { err } => { + BuildUpdate::CompilerMessage { .. } => {} + BuildUpdate::BuildReady { .. } => {} + BuildUpdate::BuildFailed { err } => { let error = err.to_string(); self.build_status.set(Status::BuildError { error: ansi_to_html::convert(&error).unwrap_or(error), @@ -264,9 +264,9 @@ impl WebServer { self.send_reload_failed().await; self.send_build_status().await; } - BuilderUpdate::StdoutReceived { msg } => {} - BuilderUpdate::StderrReceived { msg } => {} - BuilderUpdate::ProcessExited { status } => {} + BuildUpdate::StdoutReceived { msg } => {} + BuildUpdate::StderrReceived { msg } => {} + BuildUpdate::ProcessExited { status } => {} } } diff --git a/packages/cli/src/serve/update.rs b/packages/cli/src/serve/update.rs index d3034cce34..e513362a42 100644 --- a/packages/cli/src/serve/update.rs +++ b/packages/cli/src/serve/update.rs @@ -1,4 +1,4 @@ -use crate::{BuildId, BuilderUpdate, Platform, TraceMsg}; +use crate::{BuildId, BuildUpdate, Platform, TraceMsg}; use axum::extract::ws::Message as WsMessage; use std::{path::PathBuf, process::ExitStatus}; @@ -13,7 +13,7 @@ pub(crate) enum ServeUpdate { /// An update regarding the state of the build and running app from an AppBuilder BuilderUpdate { id: BuildId, - update: BuilderUpdate, + update: BuildUpdate, }, FilesChanged { From 1628e699ec7e4c31f67cc3e3d7f660a5bee92653 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 11 Apr 2025 21:26:36 -0700 Subject: [PATCH 109/301] wip: server not working anymore :( --- Cargo.lock | 49 +++++++ Cargo.toml | 2 +- packages/cli/src/build/builder.rs | 34 ++--- packages/cli/src/build/request.rs | 16 +-- packages/cli/src/serve/runner.rs | 38 ++--- packages/cli/src/serve/server.rs | 5 +- packages/fullstack/src/document/mod.rs | 14 -- packages/fullstack/src/lib.rs | 14 ++ .../fullstack/src/{document => }/server.rs | 0 packages/fullstack/src/{document => }/web.rs | 0 packages/server/Cargo.toml | 131 ++++++++++++++++++ .../src/server => server/src}/launch.rs | 57 ++++---- .../src/server/mod.rs => server/src/lib.rs} | 1 + .../src/axum_core.rs => server/src/router.rs} | 0 .../{fullstack => server}/src/serve_config.rs | 0 .../src/server_context.rs | 0 .../{fullstack => server}/src/streaming.rs | 0 17 files changed, 271 insertions(+), 90 deletions(-) delete mode 100644 packages/fullstack/src/document/mod.rs rename packages/fullstack/src/{document => }/server.rs (100%) rename packages/fullstack/src/{document => }/web.rs (100%) create mode 100644 packages/server/Cargo.toml rename packages/{fullstack/src/server => server/src}/launch.rs (64%) rename packages/{fullstack/src/server/mod.rs => server/src/lib.rs} (99%) rename packages/{fullstack/src/axum_core.rs => server/src/router.rs} (100%) rename packages/{fullstack => server}/src/serve_config.rs (100%) rename packages/{fullstack => server}/src/server_context.rs (100%) rename packages/{fullstack => server}/src/streaming.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index cead274129..35b5a3c20c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4389,6 +4389,55 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "dioxus-server" +version = "0.6.3" +dependencies = [ + "async-trait", + "aws-lc-rs", + "axum 0.7.9", + "base64 0.22.1", + "bytes", + "ciborium", + "dioxus", + "dioxus-cli-config", + "dioxus-desktop", + "dioxus-devtools", + "dioxus-fullstack-hooks", + "dioxus-fullstack-protocol", + "dioxus-history", + "dioxus-interpreter-js", + "dioxus-isrg", + "dioxus-lib", + "dioxus-mobile", + "dioxus-router", + "dioxus-ssr", + "dioxus-web", + "dioxus_server_macro", + "futures-channel", + "futures-util", + "generational-box", + "http 1.3.1", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "once_cell", + "parking_lot", + "pin-project", + "rustls 0.23.26", + "serde", + "server_fn", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.4.13", + "tower-http", + "tower-layer", + "tracing", + "tracing-futures", + "web-sys", +] + [[package]] name = "dioxus-signals" version = "0.6.3" diff --git a/Cargo.toml b/Cargo.toml index dec52e2558..e49d42f789 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,7 +125,7 @@ members = [ "packages/playwright-tests/wasm-split-harness", "packages/playwright-tests/wasm-split-harness", "packages/playwright-tests/default-features-disabled" -] +, "packages/server"] [workspace.package] version = "0.6.3" diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index def8e75827..d9fc2abea9 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -382,7 +382,7 @@ impl AppBuilder { // Set the env vars that the clients will expect // These need to be stable within a release version (ie 0.6.0) let mut envs = vec![ - (dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()), + // (dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()), ( dioxus_cli_config::DEVSERVER_IP_ENV, devserver_ip.ip().to_string(), @@ -391,19 +391,19 @@ impl AppBuilder { dioxus_cli_config::DEVSERVER_PORT_ENV, devserver_ip.port().to_string(), ), - ( - dioxus_cli_config::SESSION_CACHE_DIR, - self.build.session_cache_dir().display().to_string(), - ), + // ( + // dioxus_cli_config::SESSION_CACHE_DIR, + // self.build.session_cache_dir().display().to_string(), + // ), // unset the cargo dirs in the event we're running `dx` locally // since the child process will inherit the env vars, we don't want to confuse the downstream process ("CARGO_MANIFEST_DIR", "".to_string()), ("RUST_BACKTRACE", "1".to_string()), ]; - if let Some(base_path) = &krate.config.web.app.base_path { - envs.push((dioxus_cli_config::ASSET_ROOT_ENV, base_path.clone())); - } + // if let Some(base_path) = &krate.config.web.app.base_path { + // envs.push((dioxus_cli_config::ASSET_ROOT_ENV, base_path.clone())); + // } // Launch the server if we were given an address to start it on, and the build includes a server. After we // start the server, consume its stdout/stderr. @@ -445,6 +445,7 @@ impl AppBuilder { // If we have a running process, we need to attach to it and wait for its outputs if let Some(mut child) = running_process { + tracing::debug!("setting up child process: {:#?}", child); let stdout = BufReader::new(child.stdout.take().unwrap()); let stderr = BufReader::new(child.stderr.take().unwrap()); self.stdout = Some(stdout.lines()); @@ -456,22 +457,12 @@ impl AppBuilder { } /// Gracefully kill the process and all of its children + /// /// Uses the `SIGTERM` signal on unix and `taskkill` on windows. /// This complex logic is necessary for things like window state preservation to work properly. /// /// Also wipes away the entropy executables if they exist. - pub(crate) async fn cleanup(&mut self) { - // Soft-kill the process by sending a sigkill, allowing the process to clean up - self.soft_kill().await; - - // // Wipe out the entropy executables if they exist - // if let Some(entropy_app_exe) = self.entropy_app_exe.take() { - // _ = std::fs::remove_file(entropy_app_exe); - // } - } - - /// Kill the app and server exes pub(crate) async fn soft_kill(&mut self) { use futures_util::FutureExt; @@ -506,6 +497,11 @@ impl AppBuilder { _ = process.wait().fuse() => {} _ = tokio::time::sleep(std::time::Duration::from_millis(1000)).fuse() => {} }; + + // // Wipe out the entropy executables if they exist + // if let Some(entropy_app_exe) = self.entropy_app_exe.take() { + // _ = std::fs::remove_file(entropy_app_exe); + // } } /// Hotreload an asset in the running app. diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index da1b9f2cb9..b31caf2199 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1529,14 +1529,14 @@ impl BuildRequest { 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 - if self.release { - if let Some(base_path) = &self.config.web.app.base_path { - env_vars.push((ASSET_ROOT_ENV, base_path.clone())); - } - env_vars.push((APP_TITLE_ENV, self.config.web.app.title.clone())); - } + // // If this is a release build, bake the base path and title + // // into the binary with env vars + // if self.release { + // if let Some(base_path) = &self.config.web.app.base_path { + // env_vars.push((ASSET_ROOT_ENV, base_path.clone())); + // } + // env_vars.push((APP_TITLE_ENV, self.config.web.app.title.clone())); + // } Ok(env_vars) } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index d7b6f61a74..0dfc2ccb4c 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -474,7 +474,7 @@ impl AppRunner { Platform::Server => { tracing::debug!("Opening server build"); if let Some(server) = self.server.as_mut() { - server.cleanup().await; + // server.cleanup().await; server .open( @@ -493,26 +493,26 @@ impl AppRunner { _ => { tracing::debug!("Opening client build"); - self.client.cleanup().await; + // self.client.cleanup().await; - // Start the new app before we kill the old one to give it a little bit of time - let open_browser = self.builds_opened == 0 && self.open_browser; - let always_on_top = self.always_on_top; + // // Start the new app before we kill the old one to give it a little bit of time + // let open_browser = self.builds_opened == 0 && self.open_browser; + // let always_on_top = self.always_on_top; - self.client - .open( - devserver_ip, - displayed_address, - fullstack_address, - open_browser, - always_on_top, - ) - .await?; + // self.client + // .open( + // devserver_ip, + // displayed_address, + // fullstack_address, + // open_browser, + // always_on_top, + // ) + // .await?; - self.builds_opened += 1; + // self.builds_opened += 1; - // Save the artifacts and clear the patches(?) - self.client.artifacts = Some(app); + // // Save the artifacts and clear the patches(?) + // self.client.artifacts = Some(app); } } @@ -550,10 +550,10 @@ impl AppRunner { /// Shutdown all the running processes pub(crate) async fn cleanup_all(&mut self) { - self.client.cleanup().await; + self.client.soft_kill().await; if let Some(server) = self.server.as_mut() { - server.cleanup().await; + server.soft_kill().await; } // If the client is running on Android, we need to remove the port forwarding diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 6e283a70b4..27cf05bc7c 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -67,7 +67,8 @@ pub(crate) struct WebServer { platform: Platform, } -pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); +pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); +// pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); impl WebServer { /// Start the development server. @@ -392,7 +393,7 @@ async fn devserver_mainloop( // If we're using rustls, we need to get the cert/key paths and then set up rustls let (cert_path, key_path) = get_rustls(&https_cfg).await?; - let rustls = RustlsConfig::from_pem_file(cert_path, key_path).await?; + let rustls = axum_server::tls_rustls::RustlsConfig::from_pem_file(cert_path, key_path).await?; axum_server::from_tcp_rustls(listener, rustls) .serve(router.into_make_service()) diff --git a/packages/fullstack/src/document/mod.rs b/packages/fullstack/src/document/mod.rs deleted file mode 100644 index 153cb0d85d..0000000000 --- a/packages/fullstack/src/document/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This module contains the document providers for the fullstack platform. - -#[cfg(feature = "server")] -pub mod server; -use dioxus_fullstack_protocol::SerializeContextEntry; -#[cfg(feature = "server")] -pub use server::ServerDocument; -#[cfg(all(feature = "web", feature = "document"))] -pub mod web; - -#[allow(unused)] -pub(crate) fn head_element_hydration_entry() -> SerializeContextEntry { - dioxus_fullstack_protocol::serialize_context().create_entry() -} diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 9284d8a2e6..9ca3849d1d 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -6,6 +6,20 @@ pub use once_cell; +#[cfg(feature = "server")] +pub mod server; +use dioxus_fullstack_protocol::SerializeContextEntry; +#[cfg(feature = "server")] +pub use server::ServerDocument; + +#[cfg(all(feature = "web", feature = "document"))] +pub mod web; + +#[allow(unused)] +pub(crate) fn head_element_hydration_entry() -> SerializeContextEntry { + dioxus_fullstack_protocol::serialize_context().create_entry() +} + #[cfg(feature = "axum")] #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] pub mod server; diff --git a/packages/fullstack/src/document/server.rs b/packages/fullstack/src/server.rs similarity index 100% rename from packages/fullstack/src/document/server.rs rename to packages/fullstack/src/server.rs diff --git a/packages/fullstack/src/document/web.rs b/packages/fullstack/src/web.rs similarity index 100% rename from packages/fullstack/src/document/web.rs rename to packages/fullstack/src/web.rs diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml new file mode 100644 index 0000000000..738180c79c --- /dev/null +++ b/packages/server/Cargo.toml @@ -0,0 +1,131 @@ +[package] +name = "dioxus-server" +authors = ["Jonathan Kelley", "Evan Almloff"] +version = { workspace = true } +edition = "2021" +description = "Fullstack utilities for Dioxus: Build fullstack web, desktop, and mobile apps with a single codebase." +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["web", "desktop", "mobile", "gui", "server"] +resolver = "2" + +[dependencies] +# server functions +server_fn = { version = "0.7.3", features = ["json", "url", "browser"], default-features = false } +dioxus_server_macro = { workspace = true } + +# axum +axum = { workspace = true, optional = true } +tower-http = { workspace = true, optional = true, features = ["fs"] } + +dioxus-lib = { workspace = true } +generational-box = { workspace = true } + +# Dioxus + SSR +dioxus-ssr = { workspace = true, optional = true } +dioxus-isrg = { workspace = true, optional = true } +dioxus-router = { workspace = true, features = ["streaming"], optional = true } +dioxus-fullstack-hooks = { workspace = true } +dioxus-fullstack-protocol = { workspace = true } +hyper = { workspace = true, optional = true } +http = { workspace = true, optional = true } + +# Web Integration +dioxus-web = { workspace = true, features = ["hydrate"], default-features = false, optional = true } +dioxus-interpreter-js = { workspace = true, optional = true } + +# Desktop Integration +dioxus-desktop = { workspace = true, optional = true } + +# Mobile Integration +dioxus-mobile = { workspace = true, optional = true } + +tracing = { workspace = true } +tracing-futures = { workspace = true, optional = true } +once_cell = { workspace = true } +tokio-util = { version = "0.7.8", features = ["rt"], optional = true } +async-trait = { version = "0.1.58", optional = true } + +serde = "1.0.159" +tokio-stream = { version = "0.1.12", features = ["sync"], optional = true } +futures-util = { workspace = true } +futures-channel = { workspace = true } +ciborium = { workspace = true } +base64 = { workspace = true } +rustls = { workspace = true, optional = true } +hyper-rustls = { workspace = true, optional = true } + +pin-project = { version = "1.1.2", optional = true } +thiserror = { workspace = true, optional = true } +bytes = "1.4.0" +tower = { workspace = true, features = ["util"], optional = true } +tower-layer = { version = "0.3.2", optional = true } +parking_lot = { workspace = true, features = ["send_guard"], optional = true } +web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] } + +dioxus-cli-config = { workspace = true, optional = true } + +dioxus-devtools = { workspace = true, optional = true } +aws-lc-rs = { version = "1.8.1", optional = true } +dioxus-history.workspace = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { workspace = true, features = ["rt", "sync"], optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true } + +[dev-dependencies] +dioxus = { workspace = true, features = ["fullstack"] } + +[features] +default = ["devtools", "document", "file_engine", "mounted"] +devtools = ["dioxus-web?/devtools", "dep:dioxus-devtools"] +mounted = ["dioxus-web?/mounted"] +file_engine = ["dioxus-web?/file_engine"] +document = ["dioxus-web?/document"] +web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web"] +desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +default-tls = ["server_fn/default-tls"] +rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"] +axum_core = [ + "dep:axum", + "server_fn/axum-no-default", + "dioxus_server_macro/axum", + "default-tls", + "server", +] +axum = [ + "dep:tower-http", + "server_fn/axum", + "axum_core", +] +server = [ + "server_fn/ssr", + "dioxus_server_macro/server", + "dioxus-fullstack-hooks/server", + "dep:tokio", + "dep:tokio-util", + "dep:tokio-stream", + "dep:dioxus-ssr", + "dep:dioxus-isrg", + "dep:dioxus-router", + "dep:tower", + "dep:hyper", + "dep:http", + "dep:tower-layer", + "dep:tracing-futures", + "dep:pin-project", + "dep:thiserror", + "dep:dioxus-cli-config", + "dep:async-trait", + "dep:parking_lot", + "dioxus-interpreter-js", +] +aws-lc-rs = ["dep:aws-lc-rs"] + +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] +features = ["axum", "web", "aws-lc-rs"] diff --git a/packages/fullstack/src/server/launch.rs b/packages/server/src/launch.rs similarity index 64% rename from packages/fullstack/src/server/launch.rs rename to packages/server/src/launch.rs index e73561d037..e0dd904ee7 100644 --- a/packages/fullstack/src/server/launch.rs +++ b/packages/server/src/launch.rs @@ -72,37 +72,40 @@ pub fn launch( let config = platform_config.as_ref().ok().cloned(); let dioxus_router = axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root); - let mut router; - match base_path.as_deref() { - Some(base_path) => { - let base_path = base_path.trim_matches('/'); - // If there is a base path, nest the router under it and serve the root route manually - // Nesting a route in axum only serves /base_path or /base_path/ not both - router = axum::Router::new().nest(&format!("/{base_path}/"), dioxus_router); - async fn root_render_handler( - state: State, - mut request: Request, - ) -> impl IntoResponse { - // The root of the base path always looks like the root from dioxus fullstack - *request.uri_mut() = "/".parse().unwrap(); - render_handler(state, request).await - } - if let Some(cfg) = config { - let ssr_state = SSRState::new(&cfg); - router = router.route( - &format!("/{base_path}"), - axum::routing::method_routing::get(root_render_handler).with_state( - RenderHandleState::new(cfg, root).with_ssr_state(ssr_state), - ), - ) - } - } - None => router = dioxus_router, - } + let router = dioxus_router; + // let mut router; + // match base_path.as_deref() { + // Some(base_path) => { + // let base_path = base_path.trim_matches('/'); + // // If there is a base path, nest the router under it and serve the root route manually + // // Nesting a route in axum only serves /base_path or /base_path/ not both + // router = axum::Router::new().nest(&format!("/{base_path}/"), dioxus_router); + // async fn root_render_handler( + // state: State, + // mut request: Request, + // ) -> impl IntoResponse { + // // The root of the base path always looks like the root from dioxus fullstack + // *request.uri_mut() = "/".parse().unwrap(); + // render_handler(state, request).await + // } + // if let Some(cfg) = config { + // let ssr_state = SSRState::new(&cfg); + // router = router.route( + // &format!("/{base_path}"), + // axum::routing::method_routing::get(root_render_handler).with_state( + // RenderHandleState::new(cfg, root).with_ssr_state(ssr_state), + // ), + // ) + // } + // } + // None => router = dioxus_router, + // } let router = router.into_make_service(); let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + tracing::info!("Listening on {address} with listener {listener:?}"); + axum::serve(listener, router).await.unwrap(); }); diff --git a/packages/fullstack/src/server/mod.rs b/packages/server/src/lib.rs similarity index 99% rename from packages/fullstack/src/server/mod.rs rename to packages/server/src/lib.rs index cfe8bb00de..2738397569 100644 --- a/packages/fullstack/src/server/mod.rs +++ b/packages/server/src/lib.rs @@ -56,6 +56,7 @@ //! ``` pub mod launch; +mod router; use axum::routing::*; use dioxus_lib::prelude::Element; diff --git a/packages/fullstack/src/axum_core.rs b/packages/server/src/router.rs similarity index 100% rename from packages/fullstack/src/axum_core.rs rename to packages/server/src/router.rs diff --git a/packages/fullstack/src/serve_config.rs b/packages/server/src/serve_config.rs similarity index 100% rename from packages/fullstack/src/serve_config.rs rename to packages/server/src/serve_config.rs diff --git a/packages/fullstack/src/server_context.rs b/packages/server/src/server_context.rs similarity index 100% rename from packages/fullstack/src/server_context.rs rename to packages/server/src/server_context.rs diff --git a/packages/fullstack/src/streaming.rs b/packages/server/src/streaming.rs similarity index 100% rename from packages/fullstack/src/streaming.rs rename to packages/server/src/streaming.rs From f7f2ea61e681397cbf4f029724a12a2ea10ce3c4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 13 Apr 2025 21:57:11 -0700 Subject: [PATCH 110/301] split apart "dioxus fullstack" into "dioxus server" and "dioxus web" --- packages/fullstack-hooks/src/lib.rs | 1 + packages/fullstack-protocol/src/lib.rs | 5 + packages/fullstack/src/lib.rs | 93 +++++---- packages/fullstack/src/web.rs | 18 +- packages/server/Cargo.toml | 140 +++++++------- packages/server/docs/request_origin.md | 48 +++++ .../src/server.rs => server/src/document.rs} | 2 +- packages/server/src/launch.rs | 176 +++++++++--------- packages/server/src/lib.rs | 155 ++------------- packages/{fullstack => server}/src/render.rs | 11 +- packages/server/src/router.rs | 5 +- packages/server/src/rt.rs | 139 ++++++++++++++ 12 files changed, 427 insertions(+), 366 deletions(-) create mode 100644 packages/server/docs/request_origin.md rename packages/{fullstack/src/server.rs => server/src/document.rs} (98%) rename packages/{fullstack => server}/src/render.rs (99%) create mode 100644 packages/server/src/rt.rs diff --git a/packages/fullstack-hooks/src/lib.rs b/packages/fullstack-hooks/src/lib.rs index e83a83ebf0..e151693baf 100644 --- a/packages/fullstack-hooks/src/lib.rs +++ b/packages/fullstack-hooks/src/lib.rs @@ -3,5 +3,6 @@ mod hooks; pub use hooks::*; + mod streaming; pub use streaming::*; diff --git a/packages/fullstack-protocol/src/lib.rs b/packages/fullstack-protocol/src/lib.rs index eb9e0f332f..204aceb410 100644 --- a/packages/fullstack-protocol/src/lib.rs +++ b/packages/fullstack-protocol/src/lib.rs @@ -399,3 +399,8 @@ impl std::fmt::Display for TakeDataError { } impl std::error::Error for TakeDataError {} + +/// Create a new entry in the serialize context for the head element hydration +pub fn head_element_hydration_entry() -> SerializeContextEntry { + serialize_context().create_entry() +} diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 9ca3849d1d..392812eb5e 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -6,72 +6,69 @@ pub use once_cell; -#[cfg(feature = "server")] -pub mod server; use dioxus_fullstack_protocol::SerializeContextEntry; -#[cfg(feature = "server")] -pub use server::ServerDocument; #[cfg(all(feature = "web", feature = "document"))] pub mod web; -#[allow(unused)] -pub(crate) fn head_element_hydration_entry() -> SerializeContextEntry { - dioxus_fullstack_protocol::serialize_context().create_entry() -} +// #[cfg(feature = "server")] +// pub mod server; -#[cfg(feature = "axum")] -#[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -pub mod server; +// #[cfg(feature = "server")] +// pub use server::ServerDocument; -#[cfg(feature = "axum_core")] -#[cfg_attr(docsrs, doc(cfg(feature = "axum_core")))] -pub mod axum_core; +// #[cfg(feature = "axum")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] +// pub mod server; -pub mod document; -#[cfg(feature = "server")] -mod render; -#[cfg(feature = "server")] -mod streaming; +// #[cfg(feature = "axum_core")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum_core")))] +// pub mod axum_core; -#[cfg(feature = "server")] -mod serve_config; +// pub mod document; +// #[cfg(feature = "server")] +// mod render; +// #[cfg(feature = "server")] +// mod streaming; -#[cfg(feature = "server")] -pub use serve_config::*; +// #[cfg(feature = "server")] +// mod serve_config; -#[cfg(feature = "server")] -mod server_context; +// #[cfg(feature = "server")] +// pub use serve_config::*; -/// A prelude of commonly used items in dioxus-fullstack. -pub mod prelude { - pub use dioxus_fullstack_hooks::*; +// #[cfg(feature = "server")] +// mod server_context; - #[cfg(feature = "axum")] - #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] - pub use crate::server::*; +// #[cfg(feature = "axum")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] +// pub use crate::server::*; - #[cfg(feature = "axum_core")] - pub use crate::axum_core::*; +// #[cfg(feature = "axum_core")] +// pub use crate::axum_core::*; - #[cfg(feature = "server")] - #[cfg_attr(docsrs, doc(cfg(feature = "server")))] - pub use crate::render::{FullstackHTMLTemplate, SSRState}; +// #[cfg(feature = "server")] +// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] +// pub use crate::render::{FullstackHTMLTemplate, SSRState}; - #[cfg(feature = "server")] - #[cfg_attr(docsrs, doc(cfg(feature = "server")))] - pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; +// #[cfg(feature = "server")] +// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] +// pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; - #[cfg(feature = "axum")] - #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] - pub use crate::server_context::Axum; +// #[cfg(feature = "axum")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] +// pub use crate::server_context::Axum; - #[cfg(feature = "server")] - #[cfg_attr(docsrs, doc(cfg(feature = "server")))] - pub use crate::server_context::{ - extract, server_context, with_server_context, DioxusServerContext, FromContext, - FromServerContext, ProvideServerContext, - }; +// #[cfg(feature = "server")] +// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] +// pub use crate::server_context::{ +// extract, server_context, with_server_context, DioxusServerContext, FromContext, +// FromServerContext, ProvideServerContext, +// }; + +/// A prelude of commonly used items in dioxus-fullstack. +pub mod prelude { + pub use dioxus_fullstack_hooks::*; #[cfg(feature = "server")] #[cfg_attr(docsrs, doc(cfg(feature = "server")))] diff --git a/packages/fullstack/src/web.rs b/packages/fullstack/src/web.rs index c57cb17eec..917171cc5c 100644 --- a/packages/fullstack/src/web.rs +++ b/packages/fullstack/src/web.rs @@ -1,18 +1,9 @@ -#![allow(unused)] +// #![allow(unused)] //! On the client, we use the [`WebDocument`] implementation to render the head for any elements that were not rendered on the server. use dioxus_lib::{document::*, prelude::queue_effect}; use dioxus_web::WebDocument; -use super::head_element_hydration_entry; - -fn head_element_written_on_server() -> bool { - head_element_hydration_entry() - .get() - .ok() - .unwrap_or_default() -} - /// A document provider for fullstack web clients #[derive(Clone)] pub struct FullstackWebDocument; @@ -51,3 +42,10 @@ impl Document for FullstackWebDocument { !head_element_written_on_server() } } + +fn head_element_written_on_server() -> bool { + dioxus_fullstack_protocol::head_element_hydration_entry() + .get() + .ok() + .unwrap_or_default() +} diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 738180c79c..e45f93b6ef 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -16,114 +16,102 @@ server_fn = { version = "0.7.3", features = ["json", "url", "browser"], default- dioxus_server_macro = { workspace = true } # axum -axum = { workspace = true, optional = true } -tower-http = { workspace = true, optional = true, features = ["fs"] } +axum = { workspace = true } +tower-http = { workspace = true, features = ["fs"], optional = true} dioxus-lib = { workspace = true } generational-box = { workspace = true } # Dioxus + SSR -dioxus-ssr = { workspace = true, optional = true } -dioxus-isrg = { workspace = true, optional = true } -dioxus-router = { workspace = true, features = ["streaming"], optional = true } +dioxus-ssr = { workspace = true } +dioxus-isrg = { workspace = true } +dioxus-router = { workspace = true, features = ["streaming"] } dioxus-fullstack-hooks = { workspace = true } dioxus-fullstack-protocol = { workspace = true } -hyper = { workspace = true, optional = true } -http = { workspace = true, optional = true } - -# Web Integration -dioxus-web = { workspace = true, features = ["hydrate"], default-features = false, optional = true } -dioxus-interpreter-js = { workspace = true, optional = true } - -# Desktop Integration -dioxus-desktop = { workspace = true, optional = true } - -# Mobile Integration -dioxus-mobile = { workspace = true, optional = true } +hyper = { workspace = true } +http = { workspace = true } tracing = { workspace = true } -tracing-futures = { workspace = true, optional = true } +tracing-futures = { workspace = true } once_cell = { workspace = true } -tokio-util = { version = "0.7.8", features = ["rt"], optional = true } -async-trait = { version = "0.1.58", optional = true } +tokio-util = { version = "0.7.8", features = ["rt"] } +async-trait = { version = "0.1.58" } serde = "1.0.159" -tokio-stream = { version = "0.1.12", features = ["sync"], optional = true } +tokio-stream = { version = "0.1.12", features = ["sync"] } futures-util = { workspace = true } futures-channel = { workspace = true } ciborium = { workspace = true } base64 = { workspace = true } -rustls = { workspace = true, optional = true } +rustls = { workspace = true, optional = true} hyper-rustls = { workspace = true, optional = true } -pin-project = { version = "1.1.2", optional = true } -thiserror = { workspace = true, optional = true } +pin-project = { version = "1.1.2" } +thiserror = { workspace = true } bytes = "1.4.0" -tower = { workspace = true, features = ["util"], optional = true } -tower-layer = { version = "0.3.2", optional = true } -parking_lot = { workspace = true, features = ["send_guard"], optional = true } -web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] } +tower = { workspace = true, features = ["util"] } +tower-layer = { version = "0.3.2" } +parking_lot = { workspace = true, features = ["send_guard"] } +web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] } -dioxus-cli-config = { workspace = true, optional = true } +dioxus-cli-config = { workspace = true } -dioxus-devtools = { workspace = true, optional = true } -aws-lc-rs = { version = "1.8.1", optional = true } -dioxus-history.workspace = true +dioxus-devtools = { workspace = true, optional = true} +aws-lc-rs = { version = "1.8.1", optional = true} +dioxus-history = { workspace = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -tokio = { workspace = true, features = ["rt", "sync"], optional = true } +tokio = { workspace = true, features = ["rt", "sync"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true } +tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"] } [dev-dependencies] dioxus = { workspace = true, features = ["fullstack"] } [features] -default = ["devtools", "document", "file_engine", "mounted"] -devtools = ["dioxus-web?/devtools", "dep:dioxus-devtools"] -mounted = ["dioxus-web?/mounted"] -file_engine = ["dioxus-web?/file_engine"] -document = ["dioxus-web?/document"] -web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web"] -desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] -mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +default = ["devtools"] +# default = ["devtools", "document", "file_engine", "mounted"] +devtools = ["dep:dioxus-devtools"] +# desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +# mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] default-tls = ["server_fn/default-tls"] rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"] -axum_core = [ - "dep:axum", - "server_fn/axum-no-default", - "dioxus_server_macro/axum", - "default-tls", - "server", -] -axum = [ - "dep:tower-http", - "server_fn/axum", - "axum_core", -] -server = [ - "server_fn/ssr", - "dioxus_server_macro/server", - "dioxus-fullstack-hooks/server", - "dep:tokio", - "dep:tokio-util", - "dep:tokio-stream", - "dep:dioxus-ssr", - "dep:dioxus-isrg", - "dep:dioxus-router", - "dep:tower", - "dep:hyper", - "dep:http", - "dep:tower-layer", - "dep:tracing-futures", - "dep:pin-project", - "dep:thiserror", - "dep:dioxus-cli-config", - "dep:async-trait", - "dep:parking_lot", - "dioxus-interpreter-js", -] +# document = ["d"] +# axum_core = [ +# "dep:axum", +# "server_fn/axum-no-default", +# "dioxus_server_macro/axum", +# "default-tls", +# "server", +# ] +# axum = [ +# "dep:tower-http", +# "server_fn/axum", +# "axum_core", +# ] +# server = [ +# "server_fn/ssr", +# "dioxus_server_macro/server", +# "dioxus-fullstack-hooks/server", +# "dep:tokio", +# "dep:tokio-util", +# "dep:tokio-stream", +# "dep:dioxus-ssr", +# "dep:dioxus-isrg", +# "dep:dioxus-router", +# "dep:tower", +# "dep:hyper", +# "dep:http", +# "dep:tower-layer", +# "dep:tracing-futures", +# "dep:pin-project", +# "dep:thiserror", +# "dep:dioxus-cli-config", +# "dep:async-trait", +# "dep:parking_lot", +# "dioxus-interpreter-js", +# ] aws-lc-rs = ["dep:aws-lc-rs"] [package.metadata.docs.rs] diff --git a/packages/server/docs/request_origin.md b/packages/server/docs/request_origin.md new file mode 100644 index 0000000000..0e946f4b0f --- /dev/null +++ b/packages/server/docs/request_origin.md @@ -0,0 +1,48 @@ +This method interacts with information from the current request. The request may come from: + +1. The initial SSR render if this method called from a [`Component`](dioxus_lib::prelude::component) or a [`server`](crate::prelude::server) function that is called during the initial render + +```rust +# use dioxus::prelude::*; +#[component] +fn PrintHtmlRequestInfo() -> Element { + // The server context only exists on the server, so we need to put it behind a server_only! config + server_only! { + // Since we are calling this from a component, the server context that is returned will be from + // the html request for ssr rendering + let context = server_context(); + let request_parts = context.request_parts(); + println!("headers are {:?}", request_parts.headers); + } + rsx! {} +} +``` + +2. A request to a [`server`](crate::prelude::server) function called directly from the client (either on desktop/mobile or on the web frontend after the initial render) + +```rust +# use dioxus::prelude::*; +#[server] +async fn read_headers() -> Result<(), ServerFnError> { + // Since we are calling this from a server function, the server context that is may be from the + // initial request or a request from the client + let context = server_context(); + let request_parts = context.request_parts(); + println!("headers are {:?}", request_parts.headers); + Ok(()) +} + +#[component] +fn CallServerFunction() -> Element { + rsx! { + button { + // If you click the button, the server function will be called and the server context will be + // from the client request + onclick: move |_| async { + _ = read_headers().await + }, + "Call server function" + } + } +} +``` diff --git a/packages/fullstack/src/server.rs b/packages/server/src/document.rs similarity index 98% rename from packages/fullstack/src/server.rs rename to packages/server/src/document.rs index ab7afa2bb1..5e5c4baa21 100644 --- a/packages/fullstack/src/server.rs +++ b/packages/server/src/document.rs @@ -64,7 +64,7 @@ impl ServerDocument { // We only serialize the head elements if the web document feature is enabled #[cfg(feature = "document")] { - super::head_element_hydration_entry() + dioxus_fullstack_protocol::head_element_hydration_entry() .insert(&!self.0.borrow().streaming, std::panic::Location::caller()); } } diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index e0dd904ee7..12e78ed7ff 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -10,7 +10,10 @@ use axum::{ use dioxus_cli_config::base_path; use dioxus_lib::prelude::*; -use crate::server::{render_handler, RenderHandleState, SSRState}; +use crate::{ + render_handler, rt::DioxusRouterExt, RenderHandleState, SSRState, ServeConfig, + ServeConfigBuilder, +}; /// Launch a fullstack app with the given root component, contexts, and config. #[allow(unused)] @@ -25,89 +28,96 @@ pub fn launch( tokio::runtime::Runtime::new() .unwrap() .block_on(async move { - let platform_config = platform_config - .into_iter() - .find_map(|cfg| { - cfg.downcast::() - .map(|cfg| Result::Ok(*cfg)) - .or_else(|cfg| { - cfg.downcast::() - .map(|builder| builder.build()) - }) - .ok() - }) - .unwrap_or_else(ServeConfig::new); - - // Extend the config's context providers with the context providers from the launch builder - let platform_config = platform_config.map(|mut cfg| { - let mut contexts = contexts; - let cfg_context_providers = cfg.context_providers.clone(); - for i in 0..cfg_context_providers.len() { - contexts.push(Box::new({ - let cfg_context_providers = cfg_context_providers.clone(); - move || (cfg_context_providers[i])() - })); - } - cfg.context_providers = std::sync::Arc::new(contexts); - cfg - }); - - // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address - // and we use the generated address the CLI gives us - let address = dioxus_cli_config::fullstack_address_or_localhost(); - - use crate::server::DioxusRouterExt; - - struct TryIntoResult(Result); - - impl TryInto for TryIntoResult { - type Error = crate::UnableToLoadIndex; - - fn try_into(self) -> Result { - self.0 - } - } - - let mut base_path = base_path(); - let config = platform_config.as_ref().ok().cloned(); - let dioxus_router = - axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root); - let router = dioxus_router; - // let mut router; - // match base_path.as_deref() { - // Some(base_path) => { - // let base_path = base_path.trim_matches('/'); - // // If there is a base path, nest the router under it and serve the root route manually - // // Nesting a route in axum only serves /base_path or /base_path/ not both - // router = axum::Router::new().nest(&format!("/{base_path}/"), dioxus_router); - // async fn root_render_handler( - // state: State, - // mut request: Request, - // ) -> impl IntoResponse { - // // The root of the base path always looks like the root from dioxus fullstack - // *request.uri_mut() = "/".parse().unwrap(); - // render_handler(state, request).await - // } - // if let Some(cfg) = config { - // let ssr_state = SSRState::new(&cfg); - // router = router.route( - // &format!("/{base_path}"), - // axum::routing::method_routing::get(root_render_handler).with_state( - // RenderHandleState::new(cfg, root).with_ssr_state(ssr_state), - // ), - // ) - // } - // } - // None => router = dioxus_router, - // } - - let router = router.into_make_service(); - let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - - tracing::info!("Listening on {address} with listener {listener:?}"); - - axum::serve(listener, router).await.unwrap(); + serve_server(root, contexts, platform_config).await; }); unreachable!("Launching a fullstack app should never return") } + +async fn serve_server( + root: fn() -> Result, + contexts: Vec Box + Send + Sync>>, + platform_config: Vec>, +) { + let platform_config = platform_config + .into_iter() + .find_map(|cfg| { + cfg.downcast::() + .map(|cfg| Result::Ok(*cfg)) + .or_else(|cfg| { + cfg.downcast::() + .map(|builder| builder.build()) + }) + .ok() + }) + .unwrap_or_else(ServeConfig::new); + + // Extend the config's context providers with the context providers from the launch builder + let platform_config = platform_config.map(|mut cfg| { + let mut contexts = contexts; + let cfg_context_providers = cfg.context_providers.clone(); + for i in 0..cfg_context_providers.len() { + contexts.push(Box::new({ + let cfg_context_providers = cfg_context_providers.clone(); + move || (cfg_context_providers[i])() + })); + } + cfg.context_providers = std::sync::Arc::new(contexts); + cfg + }); + + // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address + // and we use the generated address the CLI gives us + let address = dioxus_cli_config::fullstack_address_or_localhost(); + + struct TryIntoResult(Result); + + impl TryInto for TryIntoResult { + type Error = crate::UnableToLoadIndex; + + fn try_into(self) -> Result { + self.0 + } + } + + let mut base_path = base_path(); + let config = platform_config.as_ref().ok().cloned(); + let dioxus_router = + axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root); + let router = dioxus_router; + + // let mut router; + // match base_path.as_deref() { + // Some(base_path) => { + // let base_path = base_path.trim_matches('/'); + // // If there is a base path, nest the router under it and serve the root route manually + // // Nesting a route in axum only serves /base_path or /base_path/ not both + // router = axum::Router::new().nest(&format!("/{base_path}/"), dioxus_router); + // async fn root_render_handler( + // state: State, + // mut request: Request, + // ) -> impl IntoResponse { + // // The root of the base path always looks like the root from dioxus fullstack + // *request.uri_mut() = "/".parse().unwrap(); + // render_handler(state, request).await + // } + // if let Some(cfg) = config { + // let ssr_state = SSRState::new(&cfg); + // router = router.route( + // &format!("/{base_path}"), + // axum::routing::method_routing::get(root_render_handler).with_state( + // RenderHandleState::new(cfg, root).with_ssr_state(ssr_state), + // ), + // ) + // } + // } + // None => router = dioxus_router, + // } + + let router = router.into_make_service(); + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + + tracing::info!("Listening on {address} with listener {listener:?}"); + + axum::serve(listener, router).await.unwrap(); +} diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index 2738397569..f41e6548aa 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -55,146 +55,19 @@ //! } //! ``` -pub mod launch; +mod document; +mod launch; +mod render; mod router; +mod rt; +mod serve_config; +mod server_context; +mod streaming; -use axum::routing::*; -use dioxus_lib::prelude::Element; - -use crate::prelude::*; - -/// A extension trait with utilities for integrating Dioxus with your Axum router. -pub trait DioxusRouterExt: DioxusRouterFnExt { - /// Serves the static WASM for your Dioxus application (except the generated index.html). - /// - /// # Example - /// ```rust, no_run - /// # #![allow(non_snake_case)] - /// # use dioxus_lib::prelude::*; - /// # use dioxus_fullstack::prelude::*; - /// #[tokio::main] - /// async fn main() { - /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); - /// let router = axum::Router::new() - /// // Server side render the application, serve static assets, and register server functions - /// .serve_static_assets() - /// // Server render the application - /// // ... - /// .into_make_service(); - /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - /// axum::serve(listener, router).await.unwrap(); - /// } - /// ``` - fn serve_static_assets(self) -> Self - where - Self: Sized; - - /// Serves the Dioxus application. This will serve a complete server side rendered application. - /// This will serve static assets, server render the application, register server functions, and integrate with hot reloading. - /// - /// # Example - /// ```rust, no_run - /// # #![allow(non_snake_case)] - /// # use dioxus_lib::prelude::*; - /// # use dioxus_fullstack::prelude::*; - /// #[tokio::main] - /// async fn main() { - /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); - /// let router = axum::Router::new() - /// // Server side render the application, serve static assets, and register server functions - /// .serve_dioxus_application(ServeConfig::new().unwrap(), app) - /// .into_make_service(); - /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - /// axum::serve(listener, router).await.unwrap(); - /// } - /// - /// fn app() -> Element { - /// rsx! { "Hello World" } - /// } - /// ``` - fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self - where - Cfg: TryInto, - Error: std::error::Error, - Self: Sized; -} - -impl DioxusRouterExt for Router -where - S: Send + Sync + Clone + 'static, -{ - fn serve_static_assets(mut self) -> Self { - use tower_http::services::{ServeDir, ServeFile}; - - let public_path = crate::public_path(); - - if !public_path.exists() { - return self; - } - - // Serve all files in public folder except index.html - let dir = std::fs::read_dir(&public_path).unwrap_or_else(|e| { - panic!( - "Couldn't read public directory at {:?}: {}", - &public_path, e - ) - }); - - for entry in dir.flatten() { - let path = entry.path(); - if path.ends_with("index.html") { - continue; - } - let route = path - .strip_prefix(&public_path) - .unwrap() - .iter() - .map(|segment| { - segment.to_str().unwrap_or_else(|| { - panic!("Failed to convert path segment {:?} to string", segment) - }) - }) - .collect::>() - .join("/"); - let route = format!("/{}", route); - if path.is_dir() { - self = self.nest_service(&route, ServeDir::new(path).precompressed_br()); - } else { - self = self.nest_service(&route, ServeFile::new(path).precompressed_br()); - } - } - - self - } - - fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self - where - Cfg: TryInto, - Error: std::error::Error, - { - let cfg = cfg.try_into(); - let context_providers = cfg - .as_ref() - .map(|cfg| cfg.context_providers.clone()) - .unwrap_or_default(); - - // Add server functions and render index.html - let server = self - .serve_static_assets() - .register_server_functions_with_context(context_providers); - - match cfg { - Ok(cfg) => { - let ssr_state = SSRState::new(&cfg); - server.fallback( - get(render_handler) - .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)), - ) - } - Err(err) => { - tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err); - server - } - } - } -} +pub(crate) use document::*; +pub(crate) use launch::*; +pub(crate) use render::*; +pub(crate) use router::*; +pub(crate) use serve_config::*; +pub(crate) use server_context::*; +pub(crate) use streaming::*; diff --git a/packages/fullstack/src/render.rs b/packages/server/src/render.rs similarity index 99% rename from packages/fullstack/src/render.rs rename to packages/server/src/render.rs index 564eb2ea33..7893f05592 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/server/src/render.rs @@ -1,6 +1,9 @@ //! A shared pool of renderers for efficient server side rendering. -use crate::document::ServerDocument; -use crate::streaming::{Mount, StreamingRenderer}; +use crate::{document::ServerDocument, ProvideServerContext, ServeConfig}; +use crate::{ + streaming::{Mount, StreamingRenderer}, + DioxusServerContext, +}; use dioxus_cli_config::base_path; use dioxus_fullstack_hooks::{StreamingContext, StreamingStatus}; use dioxus_fullstack_protocol::{HydrationContext, SerializedHydrationData}; @@ -19,7 +22,7 @@ use std::sync::RwLock; use std::{collections::HashMap, future::Future}; use tokio::task::JoinHandle; -use crate::{prelude::*, StreamingMode}; +use crate::StreamingMode; use dioxus_lib::prelude::*; /// A suspense boundary that is pending with a placeholder in the client @@ -188,7 +191,7 @@ impl SsrRendererPool { let create_render_future = move || async move { let mut virtual_dom = virtual_dom_factory(); - let document = std::rc::Rc::new(crate::document::server::ServerDocument::default()); + let document = std::rc::Rc::new(crate::document::ServerDocument::default()); virtual_dom.provide_root_context(document.clone()); // If there is a base path, trim the base path from the route and add the base path formatting to the // history provider diff --git a/packages/server/src/router.rs b/packages/server/src/router.rs index ba752d98e1..16173ba095 100644 --- a/packages/server/src/router.rs +++ b/packages/server/src/router.rs @@ -65,9 +65,8 @@ use std::sync::Arc; -use crate::prelude::*; -use crate::render::SSRError; -use crate::ContextProviders; +use crate::{render::SSRError, with_server_context, DioxusServerContext, SSRState, ServeConfig}; +use crate::{ContextProviders, ProvideServerContext}; use axum::body; use axum::extract::State; diff --git a/packages/server/src/rt.rs b/packages/server/src/rt.rs new file mode 100644 index 0000000000..64421b56e0 --- /dev/null +++ b/packages/server/src/rt.rs @@ -0,0 +1,139 @@ +use crate::{render_handler, DioxusRouterFnExt, RenderHandleState, SSRState, ServeConfig}; +use axum::routing::*; +use dioxus_lib::prelude::Element; + +/// A extension trait with utilities for integrating Dioxus with your Axum router. +pub trait DioxusRouterExt: DioxusRouterFnExt { + /// Serves the static WASM for your Dioxus application (except the generated index.html). + /// + /// # Example + /// ```rust, no_run + /// # #![allow(non_snake_case)] + /// # use dioxus_lib::prelude::*; + /// # use dioxus_fullstack::prelude::*; + /// #[tokio::main] + /// async fn main() { + /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); + /// let router = axum::Router::new() + /// // Server side render the application, serve static assets, and register server functions + /// .serve_static_assets() + /// // Server render the application + /// // ... + /// .into_make_service(); + /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + /// axum::serve(listener, router).await.unwrap(); + /// } + /// ``` + fn serve_static_assets(self) -> Self + where + Self: Sized; + + /// Serves the Dioxus application. This will serve a complete server side rendered application. + /// This will serve static assets, server render the application, register server functions, and integrate with hot reloading. + /// + /// # Example + /// ```rust, no_run + /// # #![allow(non_snake_case)] + /// # use dioxus_lib::prelude::*; + /// # use dioxus_fullstack::prelude::*; + /// #[tokio::main] + /// async fn main() { + /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); + /// let router = axum::Router::new() + /// // Server side render the application, serve static assets, and register server functions + /// .serve_dioxus_application(ServeConfig::new().unwrap(), app) + /// .into_make_service(); + /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + /// axum::serve(listener, router).await.unwrap(); + /// } + /// + /// fn app() -> Element { + /// rsx! { "Hello World" } + /// } + /// ``` + fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self + where + Cfg: TryInto, + Error: std::error::Error, + Self: Sized; +} + +impl DioxusRouterExt for Router +where + S: Send + Sync + Clone + 'static, +{ + fn serve_static_assets(mut self) -> Self { + use tower_http::services::{ServeDir, ServeFile}; + + let public_path = crate::public_path(); + + if !public_path.exists() { + return self; + } + + // Serve all files in public folder except index.html + let dir = std::fs::read_dir(&public_path).unwrap_or_else(|e| { + panic!( + "Couldn't read public directory at {:?}: {}", + &public_path, e + ) + }); + + for entry in dir.flatten() { + let path = entry.path(); + if path.ends_with("index.html") { + continue; + } + let route = path + .strip_prefix(&public_path) + .unwrap() + .iter() + .map(|segment| { + segment.to_str().unwrap_or_else(|| { + panic!("Failed to convert path segment {:?} to string", segment) + }) + }) + .collect::>() + .join("/"); + let route = format!("/{}", route); + if path.is_dir() { + self = self.nest_service(&route, ServeDir::new(path).precompressed_br()); + } else { + self = self.nest_service(&route, ServeFile::new(path).precompressed_br()); + } + } + + self + } + + fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self + where + Cfg: TryInto, + Error: std::error::Error, + { + let cfg = cfg.try_into(); + let context_providers = cfg + .as_ref() + .map(|cfg| cfg.context_providers.clone()) + .unwrap_or_default(); + + // Add server functions and render index.html + let server = self + .serve_static_assets() + .register_server_functions_with_context(context_providers); + + match cfg { + Ok(cfg) => { + let ssr_state = SSRState::new(&cfg); + server.fallback( + get(render_handler) + .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)), + ) + } + Err(err) => { + tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err); + server + } + } + } +} From 64a25abac3d8fd29d0bc5a77b22e6e716a280bbf Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 13 Apr 2025 22:00:46 -0700 Subject: [PATCH 111/301] fixup a few more compile errors --- Cargo.lock | 3 --- packages/server/Cargo.toml | 4 +++- packages/server/src/server_context.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35b5a3c20c..01e08343eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4401,7 +4401,6 @@ dependencies = [ "ciborium", "dioxus", "dioxus-cli-config", - "dioxus-desktop", "dioxus-devtools", "dioxus-fullstack-hooks", "dioxus-fullstack-protocol", @@ -4409,10 +4408,8 @@ dependencies = [ "dioxus-interpreter-js", "dioxus-isrg", "dioxus-lib", - "dioxus-mobile", "dioxus-router", "dioxus-ssr", - "dioxus-web", "dioxus_server_macro", "futures-channel", "futures-util", diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index e45f93b6ef..51c9479dbe 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -28,6 +28,7 @@ dioxus-isrg = { workspace = true } dioxus-router = { workspace = true, features = ["streaming"] } dioxus-fullstack-hooks = { workspace = true } dioxus-fullstack-protocol = { workspace = true } +dioxus-interpreter-js = { workspace = true, optional = true} hyper = { workspace = true } http = { workspace = true } @@ -70,9 +71,10 @@ tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"] } dioxus = { workspace = true, features = ["fullstack"] } [features] -default = ["devtools"] +default = ["devtools", "document", "server_fn/axum", "server_fn/ssr", "dep:tower-http"] # default = ["devtools", "document", "file_engine", "mounted"] devtools = ["dep:dioxus-devtools"] +document = ["dep:dioxus-interpreter-js"] # desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] # mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] default-tls = ["server_fn/default-tls"] diff --git a/packages/server/src/server_context.rs b/packages/server/src/server_context.rs index a160828e90..93689d3dbb 100644 --- a/packages/server/src/server_context.rs +++ b/packages/server/src/server_context.rs @@ -275,7 +275,7 @@ mod server_fn_impl { } /// Copy the response parts to a response and mark this server context as sent - #[cfg(feature = "axum")] + // #[cfg(feature = "axum")] pub(crate) fn send_response(&self, response: &mut http::response::Response) { self.response_sent .store(true, std::sync::atomic::Ordering::Relaxed); From d5100943bd7b4bd98df90357763cac18755eb9cf Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 13 Apr 2025 22:23:34 -0700 Subject: [PATCH 112/301] grrr opening server --- Cargo.lock | 5 +- Cargo.toml | 4 +- packages/cli/src/build/request.rs | 1 + packages/cli/src/serve/runner.rs | 121 +++++++++++++++++++----------- packages/desktop/Cargo.toml | 2 +- packages/dioxus/Cargo.toml | 5 +- packages/dioxus/src/launch.rs | 6 +- packages/fullstack/src/lib.rs | 60 +-------------- packages/fullstack/src/web.rs | 2 +- packages/server/src/launch.rs | 2 - packages/server/src/lib.rs | 59 ++++++++++++++- 11 files changed, 154 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01e08343eb..61d05cd3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3539,13 +3539,14 @@ dependencies = [ "dioxus-mobile", "dioxus-native", "dioxus-router", + "dioxus-server", "dioxus-signals", "dioxus-ssr", "dioxus-web", "env_logger 0.10.2", "futures-util", "manganis", - "rand 0.8.5", + "rand 0.9.0", "serde", "thiserror 1.0.69", "tokio", @@ -3869,7 +3870,7 @@ dependencies = [ "objc", "objc_id", "once_cell", - "rand 0.8.5", + "rand 0.9.0", "reqwest 0.12.15", "rfd", "rustc-hash 1.1.0", diff --git a/Cargo.toml b/Cargo.toml index e49d42f789..0d619fe594 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ members = [ "packages/depinfo", "packages/native", "packages/asset-resolver", + "packages/server", # Playwright tests "packages/playwright-tests/liveview", @@ -125,7 +126,7 @@ members = [ "packages/playwright-tests/wasm-split-harness", "packages/playwright-tests/wasm-split-harness", "packages/playwright-tests/default-features-disabled" -, "packages/server"] +] [workspace.package] version = "0.6.3" @@ -162,6 +163,7 @@ dioxus-cli-config = { path = "packages/cli-config", version = "0.6.2" } dioxus-cli-opt = { path = "packages/cli-opt", version = "0.6.2" } dioxus-devtools = { path = "packages/devtools", version = "0.6.2" } dioxus-devtools-types = { path = "packages/devtools-types", version = "0.6.2" } +dioxus-server = { path = "packages/server", version = "0.6.2" } dioxus-fullstack = { path = "packages/fullstack", version = "0.6.2" } dioxus-fullstack-hooks = { path = "packages/fullstack-hooks", version = "0.6.3" } dioxus-fullstack-protocol = { path = "packages/fullstack-protocol", version = "0.6.3" } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index b31caf2199..4e6f7a7a46 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -347,6 +347,7 @@ pub enum BuildMode { /// /// Contains the final asset manifest, the executable, and metadata about the build. /// Note that the `exe` might be stale and/or overwritten by the time you read it! +#[derive(Clone, Debug)] pub struct BuildArtifacts { pub(crate) platform: Platform, pub(crate) exe: PathBuf, diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 0dfc2ccb4c..0fbafee9e2 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -11,6 +11,7 @@ use dioxus_core::internal::{ use dioxus_core_types::HotReloadingContext; use dioxus_devtools_types::ClientMsg; use dioxus_devtools_types::HotReloadMsg; +use dioxus_dx_wire_format::BuildStage; use dioxus_html::HtmlCtx; use dioxus_rsx::CallBody; use dioxus_rsx_hotreload::{ChangedRsx, HotReloadResult}; @@ -147,7 +148,9 @@ impl AppRunner { if fullstack { let mut build_args = args.build_arguments.clone(); build_args.platform = Some(Platform::Server); + let _server = BuildRequest::new(&build_args).await?; + // ... todo: add the server features to the server build // ... todo: add the client features to the client build // // Make sure we have a server feature if we're building a fullstack app @@ -453,13 +456,16 @@ impl AppRunner { /// Finally "bundle" this app and return a handle to it pub(crate) async fn open( &mut self, - app: BuildArtifacts, + artifacts: BuildArtifacts, devserver_ip: SocketAddr, fullstack_address: Option, displayed_address: Option, ) -> Result<()> { // Add some cute logging - let time_taken = app.time_end.duration_since(app.time_start).unwrap(); + let time_taken = artifacts + .time_end + .duration_since(artifacts.time_start) + .unwrap(); if self.builds_opened == 0 { tracing::info!( "Build completed successfully in {:?}ms, launching app! 💫", @@ -469,50 +475,51 @@ impl AppRunner { tracing::info!("Build completed in {:?}ms", time_taken.as_millis()); } - // The builds are different and need to be cleaned up independently. - match app.platform { - Platform::Server => { - tracing::debug!("Opening server build"); - if let Some(server) = self.server.as_mut() { - // server.cleanup().await; - - server - .open( - devserver_ip, - displayed_address, - fullstack_address, - false, - false, - ) - .await?; - - // Save the artifacts and clear the patches(?) - server.artifacts = Some(app); - } - } - _ => { - tracing::debug!("Opening client build"); - - // self.client.cleanup().await; - - // // Start the new app before we kill the old one to give it a little bit of time - // let open_browser = self.builds_opened == 0 && self.open_browser; - // let always_on_top = self.always_on_top; + // We can't open the server until the client is ready. + // This comes in two cases - we receive the client or the server first. + if artifacts.platform == Platform::Server && self.client.stage == BuildStage::Success { + self.open_server( + artifacts, + devserver_ip, + fullstack_address, + displayed_address, + ) + .await?; + } else + // Handle the client + if artifacts.platform != Platform::Server { + tracing::debug!("Opening client build"); + // self.client.soft_kill().await; + + // Start the new app before we kill the old one to give it a little bit of time + let open_browser = self.builds_opened == 0 && self.open_browser; + let always_on_top = self.always_on_top; + + self.client + .open( + devserver_ip, + displayed_address, + fullstack_address, + open_browser, + always_on_top, + ) + .await?; - // self.client - // .open( - // devserver_ip, - // displayed_address, - // fullstack_address, - // open_browser, - // always_on_top, - // ) - // .await?; + self.builds_opened += 1; - // self.builds_opened += 1; + // Save the artifacts and clear the patches(?) + self.client.artifacts = Some(artifacts); - // // Save the artifacts and clear the patches(?) - // self.client.artifacts = Some(app); + if let Some(server) = self.server.as_ref() { + if server.stage == BuildStage::Success { + self.open_server( + server.artifacts.clone().unwrap(), + devserver_ip, + fullstack_address, + displayed_address, + ) + .await?; + } } } @@ -953,6 +960,34 @@ impl AppRunner { self.clear_hot_reload_changes(); self.clear_cached_rsx(); } + + async fn open_server( + &mut self, + artifacts: BuildArtifacts, + devserver_ip: SocketAddr, + fullstack_address: Option, + displayed_address: Option, + ) -> Result<()> { + tracing::debug!("Opening server build"); + if let Some(server) = self.server.as_mut() { + // server.cleanup().await; + + server + .open( + devserver_ip, + displayed_address, + fullstack_address, + false, + false, + ) + .await?; + + // Save the artifacts and clear the patches(?) + server.artifacts = Some(artifacts); + } + + Ok(()) + } } /// Bind a listener to any point and return it diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index ffefedeb94..034dd78069 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -132,7 +132,7 @@ reqwest = { workspace = true, features = ["json"] } http-range = { version = "0.1.5" } dioxus-ssr = { workspace = true, default-features = false } separator = "0.4.1" -rand = { version = "0.8.4", features = ["small_rng"] } +rand = { workspace = true, features = ["small_rng"] } # These tests need to be run on the main thread, so they cannot use rust's test harness. [[test]] diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index f6c8a0b39d..0f3c46616f 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -26,6 +26,7 @@ dioxus-mobile = { workspace = true, optional = true } dioxus-desktop = { workspace = true, default-features = true, optional = true } dioxus-fullstack = { workspace = true, default-features = true, optional = true } dioxus-liveview = { workspace = true, optional = true } +dioxus-server = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true } dioxus-native = { workspace = true, optional = true } manganis = { workspace = true, features = ["dioxus"], optional = true } @@ -64,7 +65,7 @@ mobile = ["dep:dioxus-mobile", "dioxus-fullstack?/mobile", "dioxus-config-macro/ web = ["dep:dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web", "dep:dioxus-cli-config", "dioxus-cli-config?/web"] ssr = ["dep:dioxus-ssr", "dioxus-config-macro/ssr"] liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"] -server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "ssr", "dioxus-liveview?/axum"] +server = ["dep:dioxus-server", "ssr", "dioxus-liveview?/axum"] native = ["dep:dioxus-native"] # todo(jon): decompose the desktop crate such that "webview" is the default and native is opt-in # This feature just disables the no-renderer-enabled warning @@ -73,7 +74,7 @@ third-party-renderer = [] [dev-dependencies] futures-util = { workspace = true } tracing = { workspace = true } -rand = { version = "0.8.4", features = ["small_rng"] } +rand = { workspace = true, features = ["small_rng"] } criterion = { workspace = true } thiserror = { workspace = true } env_logger = "0.10.0" diff --git a/packages/dioxus/src/launch.rs b/packages/dioxus/src/launch.rs index 2f768493ee..f5ec127851 100644 --- a/packages/dioxus/src/launch.rs +++ b/packages/dioxus/src/launch.rs @@ -337,7 +337,7 @@ impl LaunchBuilder { #[cfg(feature = "server")] if matches!(platform, KnownPlatform::Server) { - return dioxus_fullstack::server::launch::launch(app, contexts, configs); + return dioxus_server::launch(app, contexts, configs); } #[cfg(feature = "web")] @@ -358,8 +358,8 @@ impl LaunchBuilder { #[cfg(feature = "document")] { - use dioxus_fullstack::document; - let document = std::rc::Rc::new(document::web::FullstackWebDocument) + use dioxus_fullstack::FullstackWebDocument; + let document = std::rc::Rc::new(FullstackWebDocument) as std::rc::Rc; vdom.provide_root_context(document); } diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 392812eb5e..32869f1674 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -6,65 +6,11 @@ pub use once_cell; -use dioxus_fullstack_protocol::SerializeContextEntry; - #[cfg(all(feature = "web", feature = "document"))] -pub mod web; - -// #[cfg(feature = "server")] -// pub mod server; - -// #[cfg(feature = "server")] -// pub use server::ServerDocument; - -// #[cfg(feature = "axum")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -// pub mod server; - -// #[cfg(feature = "axum_core")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum_core")))] -// pub mod axum_core; - -// pub mod document; -// #[cfg(feature = "server")] -// mod render; -// #[cfg(feature = "server")] -// mod streaming; - -// #[cfg(feature = "server")] -// mod serve_config; - -// #[cfg(feature = "server")] -// pub use serve_config::*; +mod web; -// #[cfg(feature = "server")] -// mod server_context; - -// #[cfg(feature = "axum")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -// pub use crate::server::*; - -// #[cfg(feature = "axum_core")] -// pub use crate::axum_core::*; - -// #[cfg(feature = "server")] -// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] -// pub use crate::render::{FullstackHTMLTemplate, SSRState}; - -// #[cfg(feature = "server")] -// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] -// pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; - -// #[cfg(feature = "axum")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -// pub use crate::server_context::Axum; - -// #[cfg(feature = "server")] -// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] -// pub use crate::server_context::{ -// extract, server_context, with_server_context, DioxusServerContext, FromContext, -// FromServerContext, ProvideServerContext, -// }; +#[cfg(all(feature = "web", feature = "document"))] +pub use web::FullstackWebDocument; /// A prelude of commonly used items in dioxus-fullstack. pub mod prelude { diff --git a/packages/fullstack/src/web.rs b/packages/fullstack/src/web.rs index 917171cc5c..f58fbe5195 100644 --- a/packages/fullstack/src/web.rs +++ b/packages/fullstack/src/web.rs @@ -1,7 +1,7 @@ // #![allow(unused)] //! On the client, we use the [`WebDocument`] implementation to render the head for any elements that were not rendered on the server. -use dioxus_lib::{document::*, prelude::queue_effect}; +use dioxus_lib::document::*; use dioxus_web::WebDocument; /// A document provider for fullstack web clients diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index 12e78ed7ff..514b325217 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -22,8 +22,6 @@ pub fn launch( contexts: Vec Box + Send + Sync>>, platform_config: Vec>, ) -> ! { - use crate::{ServeConfig, ServeConfigBuilder}; - #[cfg(not(target_arch = "wasm32"))] tokio::runtime::Runtime::new() .unwrap() diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index f41e6548aa..b4fe4a80a0 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -34,7 +34,7 @@ //! } //! //! fn app() -> Element { -//! let mut text = use_signal(|| "...".to_string()); +//! //! //! rsx! { //! button { @@ -71,3 +71,60 @@ pub(crate) use router::*; pub(crate) use serve_config::*; pub(crate) use server_context::*; pub(crate) use streaming::*; + +pub use launch::launch; + +// #[cfg(feature = "server")] +// pub mod server; + +// #[cfg(feature = "server")] +// pub use server::ServerDocument; + +// #[cfg(feature = "axum")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] +// pub mod server; + +// #[cfg(feature = "axum_core")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum_core")))] +// pub mod axum_core; + +// pub mod document; +// #[cfg(feature = "server")] +// mod render; +// #[cfg(feature = "server")] +// mod streaming; + +// #[cfg(feature = "server")] +// mod serve_config; + +// #[cfg(feature = "server")] +// pub use serve_config::*; + +// #[cfg(feature = "server")] +// mod server_context; + +// #[cfg(feature = "axum")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] +// pub use crate::server::*; + +// #[cfg(feature = "axum_core")] +// pub use crate::axum_core::*; + +// #[cfg(feature = "server")] +// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] +// pub use crate::render::{FullstackHTMLTemplate, SSRState}; + +// #[cfg(feature = "server")] +// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] +// pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; + +// #[cfg(feature = "axum")] +// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] +// pub use crate::server_context::Axum; + +// #[cfg(feature = "server")] +// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] +// pub use crate::server_context::{ +// extract, server_context, with_server_context, DioxusServerContext, FromContext, +// FromServerContext, ProvideServerContext, +// }; From a45d0c28a937bda5283fd26ca4250edc014c035e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 14 Apr 2025 01:30:09 -0700 Subject: [PATCH 113/301] simultaneous frontend and backend patching --- Cargo.lock | 3 + Cargo.toml | 1 + packages/cli/Cargo.toml | 2 +- packages/cli/src/build/builder.rs | 3 - packages/cli/src/build/request.rs | 56 ++-- packages/cli/src/serve/mod.rs | 2 +- packages/cli/src/serve/runner.rs | 89 ++--- packages/cli/src/serve/server.rs | 2 +- packages/cli/src/serve/update.rs | 5 +- packages/devtools-types/src/lib.rs | 1 + packages/devtools/src/lib.rs | 22 +- packages/dioxus/Cargo.toml | 1 + packages/dioxus/src/lib.rs | 2 + packages/server-macro/src/lib.rs | 2 +- packages/server/Cargo.toml | 4 +- packages/server/src/launch.rs | 168 ++++++++-- packages/server/src/lib.rs | 1 + packages/server/src/router.rs | 316 ------------------ packages/server/src/rt.rs | 314 ++++++++++++++++- .../subsecond-cli-support/src/lib.rs | 40 ++- packages/subsecond/subsecond/src/lib.rs | 26 +- packages/web/src/devtools.rs | 1 + 22 files changed, 636 insertions(+), 425 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61d05cd3d7..0f9f7691b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3548,6 +3548,7 @@ dependencies = [ "manganis", "rand 0.9.0", "serde", + "subsecond", "thiserror 1.0.69", "tokio", "tracing", @@ -4418,12 +4419,14 @@ dependencies = [ "http 1.3.1", "hyper 1.6.0", "hyper-rustls 0.27.5", + "hyper-util", "once_cell", "parking_lot", "pin-project", "rustls 0.23.26", "serde", "server_fn", + "subsecond", "thiserror 1.0.69", "tokio", "tokio-stream", diff --git a/Cargo.toml b/Cargo.toml index 0d619fe594..5e9678a86e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -234,6 +234,7 @@ http = "1.0.0" notify = { version = "6.1.1" } tower-http = "0.5.2" hyper = "1.0.0" +hyper-util = { version = "0.1.11" } hyper-rustls = { version= "0.27.2", default-features = false , features=["native-tokio","http1","http2","tls12","logging","ring"]} rustls = { version="0.23.12", default-features=false, features =["logging","std","tls12","ring"] } serde_json = "1.0.61" diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 3169d04b87..6cf79259ff 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -43,7 +43,7 @@ tokio-stream = "0.1.15" chrono = "0.4.19" anyhow = { workspace = true } hyper = { workspace = true } -hyper-util = "0.1.3" +hyper-util = { workspace = true } hyper-rustls = { workspace = true } rustls = { workspace = true } rayon = "1.8.0" diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index d9fc2abea9..7b15d234ed 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -412,8 +412,6 @@ impl AppBuilder { envs.push((dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string())); } - tracing::debug!("Opening app with envs: {envs:#?}"); - // We try to use stdin/stdout to communicate with the app let running_process = match self.build.platform { // Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead @@ -445,7 +443,6 @@ impl AppBuilder { // If we have a running process, we need to attach to it and wait for its outputs if let Some(mut child) = running_process { - tracing::debug!("setting up child process: {:#?}", child); let stdout = BufReader::new(child.stdout.take().unwrap()); let stderr = BufReader::new(child.stderr.take().unwrap()); self.stdout = Some(stdout.lines()); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 4e6f7a7a46..b653187dcf 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -575,7 +575,7 @@ impl BuildRequest { // Write the build artifacts to the bundle on the disk match ctx.mode { BuildMode::Thin { aslr_reference, .. } => { - self.write_patch(aslr_reference, artifacts.time_start) + self.write_patch(ctx, aslr_reference, artifacts.time_start) .await?; } @@ -978,7 +978,12 @@ impl BuildRequest { } /// Run our custom linker setup to generate a patch file in the right location - async fn write_patch(&self, aslr_reference: u64, time_start: SystemTime) -> Result<()> { + async fn write_patch( + &self, + ctx: &BuildContext, + aslr_reference: u64, + time_start: SystemTime, + ) -> Result<()> { tracing::debug!("Patching existing bundle"); let raw_args = std::fs::read_to_string(&self.link_args_file()) @@ -1083,7 +1088,7 @@ impl BuildRequest { let triple = self.triple.clone(); let mut args = vec![]; - tracing::debug!("original args:\n{}", original_args.join(" ")); + tracing::trace!("original args:\n{}", original_args.join(" ")); match triple.operating_system { // wasm32-unknown-unknown @@ -1205,22 +1210,32 @@ impl BuildRequest { )] fn build_command(&self, ctx: &BuildContext) -> Result { // Prefer using the direct rustc if we have it - if let BuildMode::Thin { direct_rustc, .. } = &ctx.mode { - tracing::debug!("Using direct rustc: {:?}", direct_rustc); - if !direct_rustc.is_empty() { - let mut cmd = Command::new(direct_rustc[0].clone()); - cmd.args(direct_rustc[1..].iter()); - cmd.envs(self.env_vars(ctx)?); - cmd.current_dir(self.workspace_dir()); - cmd.arg(format!( - "-Clinker={}", - dunce::canonicalize(std::env::current_exe().unwrap()) - .unwrap() - .display() - )); - return Ok(cmd); - } - } + // if let BuildMode::Thin { direct_rustc, .. } = &ctx.mode { + // tracing::debug!("Using direct rustc: {:?}", direct_rustc); + // if !direct_rustc.is_empty() { + // let mut cmd = Command::new("cargo"); + // cmd.args(["rustc"]); + // cmd.envs(self.env_vars(ctx)?); + // cmd.current_dir(self.workspace_dir()); + // cmd.arg(format!( + // "-Clinker={}", + // dunce::canonicalize(std::env::current_exe().unwrap()) + // .unwrap() + // .display() + // )); + // // let mut cmd = Command::new(direct_rustc[0].clone()); + // // cmd.args(direct_rustc[1..].iter()); + // // cmd.envs(self.env_vars(ctx)?); + // // cmd.current_dir(self.workspace_dir()); + // // cmd.arg(format!( + // // "-Clinker={}", + // // dunce::canonicalize(std::env::current_exe().unwrap()) + // // .unwrap() + // // .display() + // // )); + // return Ok(cmd); + // } + // } // Otherwise build up the command using cargo rustc let mut cmd = Command::new("cargo"); @@ -1294,8 +1309,7 @@ impl BuildRequest { match ctx.mode { BuildMode::Base => {} - BuildMode::Thin { .. } => {} - BuildMode::Fat => { + BuildMode::Thin { .. } | BuildMode::Fat => { // This prevents rust from passing -dead_strip to the linker // todo: don't save temps here unless we become the linker for the base app cargo_args.extend_from_slice(&[ diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 3089ea2095..7089ff6023 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -105,7 +105,7 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { // Received a message from the devtools server - currently we only use this for // logging, so we just forward it the tui - ServeUpdate::WsMessage(msg) => { + ServeUpdate::WsMessage { msg, platform } => { screen.push_ws_message(Platform::Web, &msg); _ = builder.handle_ws_message(&msg).await; } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 0fbafee9e2..bd076dd818 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -417,8 +417,12 @@ impl AppRunner { if needs_full_rebuild || true { self.client.patch_rebuild(files.to_vec()); + if let Some(server) = self.server.as_mut() { + server.patch_rebuild(files.to_vec()); + } + self.clear_hot_reload_changes(); - self.clear_cached_rsx(); + // self.clear_cached_rsx(); server.start_patch().await } else { let msg = HotReloadMsg { @@ -475,21 +479,29 @@ impl AppRunner { tracing::info!("Build completed in {:?}ms", time_taken.as_millis()); } - // We can't open the server until the client is ready. - // This comes in two cases - we receive the client or the server first. - if artifacts.platform == Platform::Server && self.client.stage == BuildStage::Success { - self.open_server( - artifacts, - devserver_ip, - fullstack_address, - displayed_address, - ) - .await?; - } else - // Handle the client - if artifacts.platform != Platform::Server { - tracing::debug!("Opening client build"); - // self.client.soft_kill().await; + // Make sure to save artifacts... + match artifacts.platform { + Platform::Server => { + if let Some(server) = self.server.as_mut() { + server.artifacts = Some(artifacts.clone()); + } else { + tracing::warn!("Server build completed but no server runner was created"); + } + } + _ => { + self.client.artifacts = Some(artifacts.clone()); + } + } + + let should_open = self.client.stage == BuildStage::Success + && (self.server.as_ref().map(|s| s.stage == BuildStage::Success)).unwrap_or(true); + + if should_open { + // Always open the server first after the client has been built + if let Some(server) = self.server.as_ref() { + self.open_server(devserver_ip, fullstack_address, displayed_address) + .await?; + } // Start the new app before we kill the old one to give it a little bit of time let open_browser = self.builds_opened == 0 && self.open_browser; @@ -506,21 +518,6 @@ impl AppRunner { .await?; self.builds_opened += 1; - - // Save the artifacts and clear the patches(?) - self.client.artifacts = Some(artifacts); - - if let Some(server) = self.server.as_ref() { - if server.stage == BuildStage::Success { - self.open_server( - server.artifacts.clone().unwrap(), - devserver_ip, - fullstack_address, - displayed_address, - ) - .await?; - } - } } Ok(()) @@ -729,7 +726,11 @@ impl AppRunner { } pub(crate) async fn patch(&mut self, res: &BuildArtifacts) -> Result { - let client = &self.client; + let client = match res.platform { + Platform::Server => &self.server.as_ref().unwrap(), + _ => &self.client, + }; + let original = client.build.main_exe(); let new = client.build.patch_exe(res.time_start); let triple = client.build.triple.clone(); @@ -780,8 +781,8 @@ impl AppRunner { .as_millis() ); - // Save this patch - self.client.patches.push(jump_table.clone()); + // // Save this patch + // self.client.patches.push(jump_table.clone()); Ok(jump_table) } @@ -800,9 +801,21 @@ impl AppRunner { .context("client message not proper encoding")?; match serde_json::from_str::(as_text) { - Ok(ClientMsg::Initialize { aslr_reference }) => { + Ok(ClientMsg::Initialize { + aslr_reference, + build_id, + }) => { tracing::debug!("Setting aslr_reference: {aslr_reference}"); - self.client.aslr_reference = Some(aslr_reference); + match build_id { + 0 => { + self.client.aslr_reference = Some(aslr_reference); + } + _ => { + if let Some(server) = self.server.as_mut() { + server.aslr_reference = Some(aslr_reference); + } + } + } } Ok(_client) => {} Err(err) => { @@ -963,7 +976,6 @@ impl AppRunner { async fn open_server( &mut self, - artifacts: BuildArtifacts, devserver_ip: SocketAddr, fullstack_address: Option, displayed_address: Option, @@ -981,9 +993,6 @@ impl AppRunner { false, ) .await?; - - // Save the artifacts and clear the patches(?) - server.artifacts = Some(artifacts); } Ok(()) diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 27cf05bc7c..dd304812d9 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -174,7 +174,7 @@ impl WebServer { } Some((idx, message)) = new_message.next() => { match message { - Some(Ok(message)) => return ServeUpdate::WsMessage(message), + Some(Ok(msg)) => return ServeUpdate::WsMessage { msg, platform: Platform::Web }, _ => { drop(new_message); _ = self.hot_reload_sockets.remove(idx); diff --git a/packages/cli/src/serve/update.rs b/packages/cli/src/serve/update.rs index e513362a42..514217a797 100644 --- a/packages/cli/src/serve/update.rs +++ b/packages/cli/src/serve/update.rs @@ -8,7 +8,10 @@ use std::{path::PathBuf, process::ExitStatus}; #[allow(clippy::large_enum_variant)] pub(crate) enum ServeUpdate { NewConnection, - WsMessage(WsMessage), + WsMessage { + platform: Platform, + msg: WsMessage, + }, /// An update regarding the state of the build and running app from an AppBuilder BuilderUpdate { diff --git a/packages/devtools-types/src/lib.rs b/packages/devtools-types/src/lib.rs index 70a102618b..9271eae13c 100644 --- a/packages/devtools-types/src/lib.rs +++ b/packages/devtools-types/src/lib.rs @@ -32,6 +32,7 @@ pub enum DevserverMsg { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub enum ClientMsg { Initialize { + build_id: u64, aslr_reference: u64, }, Log { diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index d198b1d24e..0c2c31d585 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -1,11 +1,10 @@ -use std::{any::TypeId, cell::Cell, ffi::CString, path::PathBuf, rc::Rc}; - use dioxus_core::{ prelude::{consume_context, try_consume_context}, Element, ScopeId, VirtualDom, }; pub use dioxus_devtools_types::*; use dioxus_signals::{GlobalKey, Writable}; +use std::{any::TypeId, cell::Cell, ffi::CString, path::PathBuf, rc::Rc}; use subsecond::JumpTable; use warnings::Warning; @@ -57,6 +56,10 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { }); } +pub fn apply_patch(table: JumpTable) { + unsafe { subsecond::apply_patch(table) }; +} + /// Connect to the devserver and handle its messages with a callback. /// /// This doesn't use any form of security or protocol, so it's not safe to expose to the internet. @@ -68,9 +71,10 @@ pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + Err(_) => return, }; - websocket.send(tungstenite::Message::Text( + _ = websocket.send(tungstenite::Message::Text( serde_json::to_string(&ClientMsg::Initialize { aslr_reference: subsecond::aslr_reference() as _, + build_id: build_id(), }) .unwrap(), )); @@ -84,3 +88,15 @@ pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + } }); } + +pub fn build_id() -> u64 { + #[cfg(target_arch = "wasm32")] + { + 0 + } + + #[cfg(not(target_arch = "wasm32"))] + { + 1 + } +} diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 0f3c46616f..808e2b30d7 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -33,6 +33,7 @@ manganis = { workspace = true, features = ["dioxus"], optional = true } dioxus-logger = { workspace = true, optional = true } warnings = { workspace = true, optional = true } wasm-split = { workspace = true, optional = true } +subsecond = { workspace = true } serde = { workspace = true, optional = true } dioxus-cli-config = { workspace = true, optional = true } diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index 72fb63491d..1b6ac2d4fa 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -80,6 +80,8 @@ pub use dioxus_cli_config as cli_config; #[cfg_attr(docsrs, doc(cfg(feature = "wasm-split")))] pub use wasm_split; +pub use subsecond; + pub mod prelude { #[cfg(feature = "document")] #[cfg_attr(docsrs, doc(cfg(feature = "document")))] diff --git a/packages/server-macro/src/lib.rs b/packages/server-macro/src/lib.rs index 66e9b30335..dc32b9ba01 100644 --- a/packages/server-macro/src/lib.rs +++ b/packages/server-macro/src/lib.rs @@ -148,7 +148,7 @@ use syn::{ #[proc_macro_attribute] pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { // If there is no input codec, use json as the default - let args = default_json_codec(args); + // let args = default_json_codec(args); match server_macro_impl( args.into(), diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 51c9479dbe..3d7315bc15 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -60,6 +60,8 @@ dioxus-cli-config = { workspace = true } dioxus-devtools = { workspace = true, optional = true} aws-lc-rs = { version = "1.8.1", optional = true} dioxus-history = { workspace = true } +hyper-util = { workspace = true, features = ["full"] } +subsecond.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] tokio = { workspace = true, features = ["rt", "sync"] } @@ -71,7 +73,7 @@ tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"] } dioxus = { workspace = true, features = ["fullstack"] } [features] -default = ["devtools", "document", "server_fn/axum", "server_fn/ssr", "dep:tower-http"] +default = ["devtools", "document", "server_fn/axum", "dioxus_server_macro/axum", "dioxus_server_macro/server", "dioxus-fullstack-hooks/server", "server_fn/ssr", "dep:tower-http"] # default = ["devtools", "document", "file_engine", "mounted"] devtools = ["dep:dioxus-devtools"] document = ["dep:dioxus-interpreter-js"] diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index 514b325217..5e389dbb67 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -1,27 +1,42 @@ //! A launch function that creates an axum router for the LaunchBuilder -use std::any::Any; +use std::{any::Any, net::SocketAddr}; use axum::{ body::Body, extract::{Request, State}, response::IntoResponse, + routing::IntoMakeService, + serve::IncomingStream, }; use dioxus_cli_config::base_path; +use dioxus_devtools::DevserverMsg; use dioxus_lib::prelude::*; +use futures_util::{stream::FusedStream, StreamExt}; +use hyper::body::Incoming; +use hyper_util::server::conn::auto::Builder as HyperBuilder; +use hyper_util::{ + rt::{TokioExecutor, TokioIo}, + service::TowerToHyperService, +}; +use tokio::net::TcpStream; +use tokio_util::task::LocalPoolHandle; +use tower::Service; +use tower::ServiceExt as _; +// use tower::{Service, ServiceExt}; use crate::{ render_handler, rt::DioxusRouterExt, RenderHandleState, SSRState, ServeConfig, ServeConfigBuilder, }; +type ContextList = Vec Box + Send + Sync>>; + +type BaseComp = fn() -> Element; + /// Launch a fullstack app with the given root component, contexts, and config. #[allow(unused)] -pub fn launch( - root: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - platform_config: Vec>, -) -> ! { +pub fn launch(root: BaseComp, contexts: ContextList, platform_config: Vec>) -> ! { #[cfg(not(target_arch = "wasm32"))] tokio::runtime::Runtime::new() .unwrap() @@ -37,6 +52,14 @@ async fn serve_server( contexts: Vec Box + Send + Sync>>, platform_config: Vec>, ) { + let (devtools_tx, mut devtools_rx) = futures_channel::mpsc::unbounded(); + + if let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() { + dioxus_devtools::connect(endpoint, move |msg| { + _ = devtools_tx.unbounded_send(msg); + }) + } + let platform_config = platform_config .into_iter() .find_map(|cfg| { @@ -68,22 +91,135 @@ async fn serve_server( // and we use the generated address the CLI gives us let address = dioxus_cli_config::fullstack_address_or_localhost(); - struct TryIntoResult(Result); + let config = platform_config.as_ref().ok().cloned(); - impl TryInto for TryIntoResult { - type Error = crate::UnableToLoadIndex; + let router = build_router(root, platform_config); + let task_pool = LocalPoolHandle::new(5); + let make_service = router.into_make_service(); - fn try_into(self) -> Result { - self.0 + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + + tracing::info!("Listening on {address} with listener {listener:?}"); + + enum Msg { + TcpStream(std::io::Result<(TcpStream, SocketAddr)>), + Devtools(DevserverMsg), + } + + // axum::serve() + + // Manually loop on accepting connections so we can also respond to devtools messages + loop { + let res = tokio::select! { + res = listener.accept() => Msg::TcpStream(res), + msg = devtools_rx.next(), if !devtools_rx.is_terminated() => { + if let Some(msg) = msg { + Msg::Devtools(msg) + } else { + continue; + } + } + }; + + match res { + // We need to delete our old router and build a new one + // + // one challenge is that the server functions are sitting in the dlopened lib and no longer + // accessible by us (the original process) + // + // We need to somehow get them out... ? + // + // for now we just support editing existing server functions + Msg::Devtools(devserver_msg) => match devserver_msg { + DevserverMsg::HotReload(hot_reload_msg) => { + if let Some(table) = hot_reload_msg.jump_table { + dioxus_devtools::apply_patch(table); + } + } + DevserverMsg::FullReloadStart => {} + DevserverMsg::FullReloadFailed => {} + DevserverMsg::FullReloadCommand => {} + DevserverMsg::Shutdown => {} + }, + Msg::TcpStream(Err(_)) => {} + Msg::TcpStream(Ok((tcp_stream, remote_addr))) => { + let mut make_service = make_service.clone(); + task_pool.spawn_pinned(move || async move { + let tcp_stream = TokioIo::new(tcp_stream); + + std::future::poll_fn(|cx| { + as tower::Service>::poll_ready( + &mut make_service, + cx, + ) + }) + .await + .unwrap_or_else(|err| match err {}); + + // todo - this was taken from axum::serve but it seems like IncomingStream serves no purpose? + #[derive(Debug)] + pub struct IncomingStream_<'a> { + tcp_stream: &'a TokioIo, + remote_addr: SocketAddr, + } + + let tower_service = make_service + .call(IncomingStream_ { + tcp_stream: &tcp_stream, + remote_addr, + }) + .await + .unwrap_or_else(|err| match err {}) + .map_request(|req: Request| { + let req = req.map(Body::new); + + tracing::info!("Handling request: {:?}", req); + + req + }); + + // upgrades needed for websockets + let ret = HyperBuilder::new(TokioExecutor::new()) + .serve_connection_with_upgrades( + tcp_stream, + TowerToHyperService::new(tower_service), + ) + .await; + + if let Err(err) = ret { + // This error only appears when the client doesn't send a request and + // terminate the connection. + // + // If client sends one request then terminate connection whenever, it doesn't + // appear. + } + }); + } } } +} +fn build_router( + root: fn() -> Result, + platform_config: Result, +) -> axum::Router { let mut base_path = base_path(); - let config = platform_config.as_ref().ok().cloned(); + let dioxus_router = axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root); + let router = dioxus_router; + struct TryIntoResult(Result); + + impl TryInto for TryIntoResult { + type Error = crate::UnableToLoadIndex; + + fn try_into(self) -> Result { + self.0 + } + } + // let mut router; // match base_path.as_deref() { // Some(base_path) => { @@ -111,11 +247,5 @@ async fn serve_server( // } // None => router = dioxus_router, // } - - let router = router.into_make_service(); - let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - - tracing::info!("Listening on {address} with listener {listener:?}"); - - axum::serve(listener, router).await.unwrap(); + router } diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index b4fe4a80a0..c932f148f1 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -68,6 +68,7 @@ pub(crate) use document::*; pub(crate) use launch::*; pub(crate) use render::*; pub(crate) use router::*; +pub(crate) use rt::*; pub(crate) use serve_config::*; pub(crate) use server_context::*; pub(crate) use streaming::*; diff --git a/packages/server/src/router.rs b/packages/server/src/router.rs index 16173ba095..efc1cba7c9 100644 --- a/packages/server/src/router.rs +++ b/packages/server/src/router.rs @@ -62,319 +62,3 @@ //! //! These utilities compile to the WASM family of targets, while the more complete ones found in [server] don't //! ``` - -use std::sync::Arc; - -use crate::{render::SSRError, with_server_context, DioxusServerContext, SSRState, ServeConfig}; -use crate::{ContextProviders, ProvideServerContext}; - -use axum::body; -use axum::extract::State; -use axum::routing::*; -use axum::{ - body::Body, - http::{Request, Response, StatusCode}, - response::IntoResponse, -}; -use dioxus_lib::prelude::{Element, VirtualDom}; -use http::header::*; - -/// A extension trait with server function utilities for integrating Dioxus with your Axum router. -pub trait DioxusRouterFnExt { - /// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions. - /// - /// # Example - /// ```rust, no_run - /// # use dioxus_lib::prelude::*; - /// # use dioxus_fullstack::prelude::*; - /// #[tokio::main] - /// async fn main() { - /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); - /// let router = axum::Router::new() - /// // Register server functions routes with the default handler - /// .register_server_functions() - /// .into_make_service(); - /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - /// axum::serve(listener, router).await.unwrap(); - /// } - /// ``` - #[allow(dead_code)] - fn register_server_functions(self) -> Self - where - Self: Sized, - { - self.register_server_functions_with_context(Default::default()) - } - - /// Registers server functions with some additional context to insert into the [`DioxusServerContext`] for that handler. - /// - /// # Example - /// ```rust, no_run - /// # use dioxus_lib::prelude::*; - /// # use dioxus_fullstack::prelude::*; - /// # use std::sync::Arc; - /// #[tokio::main] - /// async fn main() { - /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); - /// let router = axum::Router::new() - /// // Register server functions routes with the default handler - /// .register_server_functions_with_context(Arc::new(vec![Box::new(|| Box::new(1234567890u32))])) - /// .into_make_service(); - /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - /// axum::serve(listener, router).await.unwrap(); - /// } - /// ``` - fn register_server_functions_with_context(self, context_providers: ContextProviders) -> Self; -} - -impl DioxusRouterFnExt for Router -where - S: Send + Sync + Clone + 'static, -{ - fn register_server_functions_with_context( - mut self, - context_providers: ContextProviders, - ) -> Self { - use http::method::Method; - - for (path, method) in server_fn::axum::server_fn_paths() { - tracing::trace!("Registering server function: {} {}", method, path); - let context_providers = context_providers.clone(); - let handler = move |req| handle_server_fns_inner(path, context_providers, req); - self = match method { - Method::GET => self.route(path, get(handler)), - Method::POST => self.route(path, post(handler)), - Method::PUT => self.route(path, put(handler)), - _ => unimplemented!("Unsupported server function method: {}", method), - }; - } - - self - } -} - -/// A handler for Dioxus server functions. This will run the server function and return the result. -async fn handle_server_fns_inner( - path: &str, - additional_context: ContextProviders, - req: Request, -) -> impl IntoResponse { - use server_fn::middleware::Service; - - let path_string = path.to_string(); - - let (parts, body) = req.into_parts(); - let req = Request::from_parts(parts.clone(), body); - let method = req.method().clone(); - - if let Some(mut service) = - server_fn::axum::get_server_fn_service(&path_string, method) - { - // Create the server context with info from the request - let server_context = DioxusServerContext::new(parts); - // Provide additional context from the render state - add_server_context(&server_context, &additional_context); - - // store Accepts and Referrer in case we need them for redirect (below) - let accepts_html = req - .headers() - .get(ACCEPT) - .and_then(|v| v.to_str().ok()) - .map(|v| v.contains("text/html")) - .unwrap_or(false); - let referrer = req.headers().get(REFERER).cloned(); - - // actually run the server fn (which may use the server context) - let fut = with_server_context(server_context.clone(), || service.run(req)); - let mut res = ProvideServerContext::new(fut, server_context.clone()).await; - - // it it accepts text/html (i.e., is a plain form post) and doesn't already have a - // Location set, then redirect to Referer - if accepts_html { - if let Some(referrer) = referrer { - let has_location = res.headers().get(LOCATION).is_some(); - if !has_location { - *res.status_mut() = StatusCode::FOUND; - res.headers_mut().insert(LOCATION, referrer); - } - } - } - - // apply the response parts from the server context to the response - server_context.send_response(&mut res); - - Ok(res) - } else { - Response::builder().status(StatusCode::BAD_REQUEST).body( - { - #[cfg(target_family = "wasm")] - { - Body::from(format!( - "No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.", - )) - } - #[cfg(not(target_family = "wasm"))] - { - Body::from(format!( - "No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.", - )) - } - } - ) - } - .expect("could not build Response") -} - -pub(crate) fn add_server_context( - server_context: &DioxusServerContext, - context_providers: &ContextProviders, -) { - for index in 0..context_providers.len() { - let context_providers = context_providers.clone(); - server_context.insert_boxed_factory(Box::new(move || context_providers[index]())); - } -} - -/// State used by [`render_handler`] to render a dioxus component with axum -#[derive(Clone)] -pub struct RenderHandleState { - config: ServeConfig, - build_virtual_dom: Arc VirtualDom + Send + Sync>, - ssr_state: once_cell::sync::OnceCell, -} - -impl RenderHandleState { - /// Create a new [`RenderHandleState`] - pub fn new(config: ServeConfig, root: fn() -> Element) -> Self { - Self { - config, - build_virtual_dom: Arc::new(move || VirtualDom::new(root)), - ssr_state: Default::default(), - } - } - - /// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be used to pass context into the root component of your application. - pub fn new_with_virtual_dom_factory( - config: ServeConfig, - build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static, - ) -> Self { - Self { - config, - build_virtual_dom: Arc::new(build_virtual_dom), - ssr_state: Default::default(), - } - } - - /// Set the [`ServeConfig`] for this [`RenderHandleState`] - pub fn with_config(mut self, config: ServeConfig) -> Self { - self.config = config; - self - } - - /// Set the [`SSRState`] for this [`RenderHandleState`]. Sharing a [`SSRState`] between multiple [`RenderHandleState`]s is more efficient than creating a new [`SSRState`] for each [`RenderHandleState`]. - pub fn with_ssr_state(mut self, ssr_state: SSRState) -> Self { - self.ssr_state = once_cell::sync::OnceCell::new(); - if self.ssr_state.set(ssr_state).is_err() { - panic!("SSRState already set"); - } - self - } - - fn ssr_state(&self) -> &SSRState { - self.ssr_state.get_or_init(|| SSRState::new(&self.config)) - } -} - -/// SSR renderer handler for Axum with added context injection. -/// -/// # Example -/// ```rust,no_run -/// #![allow(non_snake_case)] -/// use std::sync::{Arc, Mutex}; -/// -/// use axum::routing::get; -/// use dioxus::prelude::*; -/// -/// fn app() -> Element { -/// rsx! { -/// "hello!" -/// } -/// } -/// -/// #[tokio::main] -/// async fn main() { -/// let addr = dioxus::cli_config::fullstack_address_or_localhost(); -/// let router = axum::Router::new() -/// // Register server functions, etc. -/// // Note you can use `register_server_functions_with_context` -/// // to inject the context into server functions running outside -/// // of an SSR render context. -/// .fallback(get(render_handler) -/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app)) -/// ) -/// .into_make_service(); -/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); -/// axum::serve(listener, router).await.unwrap(); -/// } -/// ``` -pub async fn render_handler( - State(state): State, - request: Request, -) -> impl IntoResponse { - let cfg = &state.config; - let ssr_state = state.ssr_state(); - let build_virtual_dom = { - let build_virtual_dom = state.build_virtual_dom.clone(); - let context_providers = state.config.context_providers.clone(); - move || { - let mut vdom = build_virtual_dom(); - for state in context_providers.as_slice() { - vdom.insert_any_root_context(state()); - } - vdom - } - }; - - let (parts, _) = request.into_parts(); - let url = parts - .uri - .path_and_query() - .ok_or(StatusCode::BAD_REQUEST)? - .to_string(); - let parts: Arc> = - Arc::new(parking_lot::RwLock::new(parts)); - // Create the server context with info from the request - let server_context = DioxusServerContext::from_shared_parts(parts.clone()); - // Provide additional context from the render state - add_server_context(&server_context, &state.config.context_providers); - - match ssr_state - .render(url, cfg, build_virtual_dom, &server_context) - .await - { - Ok((freshness, rx)) => { - let mut response = axum::response::Html::from(Body::from_stream(rx)).into_response(); - freshness.write(response.headers_mut()); - server_context.send_response(&mut response); - Result::, StatusCode>::Ok(response) - } - Err(SSRError::Incremental(e)) => { - tracing::error!("Failed to render page: {}", e); - Ok(report_err(e).into_response()) - } - Err(SSRError::Routing(e)) => { - tracing::trace!("Page not found: {}", e); - Ok(Response::builder() - .status(StatusCode::NOT_FOUND) - .body(Body::from("Page not found")) - .unwrap()) - } - } -} - -fn report_err(e: E) -> Response { - Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(body::Body::new(format!("Error: {}", e))) - .unwrap() -} diff --git a/packages/server/src/rt.rs b/packages/server/src/rt.rs index 64421b56e0..1cc7723081 100644 --- a/packages/server/src/rt.rs +++ b/packages/server/src/rt.rs @@ -1,6 +1,16 @@ -use crate::{render_handler, DioxusRouterFnExt, RenderHandleState, SSRState, ServeConfig}; +use crate::{render::SSRError, with_server_context, DioxusServerContext, SSRState, ServeConfig}; +use crate::{ContextProviders, ProvideServerContext}; +use axum::body; +use axum::extract::State; use axum::routing::*; -use dioxus_lib::prelude::Element; +use axum::{ + body::Body, + http::{Request, Response, StatusCode}, + response::IntoResponse, +}; +use dioxus_lib::prelude::{Element, VirtualDom}; +use http::header::*; +use std::sync::Arc; /// A extension trait with utilities for integrating Dioxus with your Axum router. pub trait DioxusRouterExt: DioxusRouterFnExt { @@ -137,3 +147,303 @@ where } } } + +/// A extension trait with server function utilities for integrating Dioxus with your Axum router. +pub trait DioxusRouterFnExt { + /// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions. + /// + /// # Example + /// ```rust, no_run + /// # use dioxus_lib::prelude::*; + /// # use dioxus_fullstack::prelude::*; + /// #[tokio::main] + /// async fn main() { + /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); + /// let router = axum::Router::new() + /// // Register server functions routes with the default handler + /// .register_server_functions() + /// .into_make_service(); + /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + /// axum::serve(listener, router).await.unwrap(); + /// } + /// ``` + #[allow(dead_code)] + fn register_server_functions(self) -> Self + where + Self: Sized, + { + self.register_server_functions_with_context(Default::default()) + } + + /// Registers server functions with some additional context to insert into the [`DioxusServerContext`] for that handler. + /// + /// # Example + /// ```rust, no_run + /// # use dioxus_lib::prelude::*; + /// # use dioxus_fullstack::prelude::*; + /// # use std::sync::Arc; + /// #[tokio::main] + /// async fn main() { + /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); + /// let router = axum::Router::new() + /// // Register server functions routes with the default handler + /// .register_server_functions_with_context(Arc::new(vec![Box::new(|| Box::new(1234567890u32))])) + /// .into_make_service(); + /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + /// axum::serve(listener, router).await.unwrap(); + /// } + /// ``` + fn register_server_functions_with_context(self, context_providers: ContextProviders) -> Self; +} + +impl DioxusRouterFnExt for Router +where + S: Send + Sync + Clone + 'static, +{ + fn register_server_functions_with_context( + mut self, + context_providers: ContextProviders, + ) -> Self { + use http::method::Method; + + tracing::info!("Registering server functions..."); + + for (path, method) in server_fn::axum::server_fn_paths() { + tracing::info!("Registering server function: {} {}", method, path); + let context_providers = context_providers.clone(); + let handler = move |req| handle_server_fns_inner(path, context_providers, req); + self = match method { + Method::GET => self.route(path, get(handler)), + Method::POST => self.route(path, post(handler)), + Method::PUT => self.route(path, put(handler)), + _ => unimplemented!("Unsupported server function method: {}", method), + }; + } + + self + } +} + +/// A handler for Dioxus server functions. This will run the server function and return the result. +async fn handle_server_fns_inner( + path: &str, + additional_context: ContextProviders, + req: Request, +) -> Response { + use server_fn::middleware::Service; + + let path_string = path.to_string(); + + let (parts, body) = req.into_parts(); + let req = Request::from_parts(parts.clone(), body); + let method = req.method().clone(); + + let Some(mut service) = server_fn::axum::get_server_fn_service(&path_string, method) else { + return Response::builder().status(StatusCode::BAD_REQUEST).body( + { + #[cfg(target_family = "wasm")] + { + Body::from(format!( + "No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.", + )) + } + #[cfg(not(target_family = "wasm"))] + { + Body::from(format!( + "No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.", + )) + } + } + ).unwrap(); + }; + + // Create the server context with info from the request + let server_context = DioxusServerContext::new(parts); + // Provide additional context from the render state + add_server_context(&server_context, &additional_context); + + // store Accepts and Referrer in case we need them for redirect (below) + let accepts_html = req + .headers() + .get(ACCEPT) + .and_then(|v| v.to_str().ok()) + .map(|v| v.contains("text/html")) + .unwrap_or(false); + let referrer = req.headers().get(REFERER).cloned(); + + // actually run the server fn (which may use the server context) + let fut = with_server_context(server_context.clone(), || service.run(req)); + + let mut res = ProvideServerContext::new(fut, server_context.clone()).await; + + // it it accepts text/html (i.e., is a plain form post) and doesn't already have a + // Location set, then redirect to Referer + if accepts_html { + if let Some(referrer) = referrer { + let has_location = res.headers().get(LOCATION).is_some(); + if !has_location { + *res.status_mut() = StatusCode::FOUND; + res.headers_mut().insert(LOCATION, referrer); + } + } + } + + // apply the response parts from the server context to the response + server_context.send_response(&mut res); + + res +} + +pub(crate) fn add_server_context( + server_context: &DioxusServerContext, + context_providers: &ContextProviders, +) { + for index in 0..context_providers.len() { + let context_providers = context_providers.clone(); + server_context.insert_boxed_factory(Box::new(move || context_providers[index]())); + } +} + +/// State used by [`render_handler`] to render a dioxus component with axum +#[derive(Clone)] +pub struct RenderHandleState { + config: ServeConfig, + build_virtual_dom: Arc VirtualDom + Send + Sync>, + ssr_state: once_cell::sync::OnceCell, +} + +impl RenderHandleState { + /// Create a new [`RenderHandleState`] + pub fn new(config: ServeConfig, root: fn() -> Element) -> Self { + Self { + config, + build_virtual_dom: Arc::new(move || VirtualDom::new(root)), + ssr_state: Default::default(), + } + } + + /// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be used to pass context into the root component of your application. + pub fn new_with_virtual_dom_factory( + config: ServeConfig, + build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static, + ) -> Self { + Self { + config, + build_virtual_dom: Arc::new(build_virtual_dom), + ssr_state: Default::default(), + } + } + + /// Set the [`ServeConfig`] for this [`RenderHandleState`] + pub fn with_config(mut self, config: ServeConfig) -> Self { + self.config = config; + self + } + + /// Set the [`SSRState`] for this [`RenderHandleState`]. Sharing a [`SSRState`] between multiple [`RenderHandleState`]s is more efficient than creating a new [`SSRState`] for each [`RenderHandleState`]. + pub fn with_ssr_state(mut self, ssr_state: SSRState) -> Self { + self.ssr_state = once_cell::sync::OnceCell::new(); + if self.ssr_state.set(ssr_state).is_err() { + panic!("SSRState already set"); + } + self + } + + fn ssr_state(&self) -> &SSRState { + self.ssr_state.get_or_init(|| SSRState::new(&self.config)) + } +} + +/// SSR renderer handler for Axum with added context injection. +/// +/// # Example +/// ```rust,no_run +/// #![allow(non_snake_case)] +/// use std::sync::{Arc, Mutex}; +/// +/// use axum::routing::get; +/// use dioxus::prelude::*; +/// +/// fn app() -> Element { +/// rsx! { +/// "hello!" +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let addr = dioxus::cli_config::fullstack_address_or_localhost(); +/// let router = axum::Router::new() +/// // Register server functions, etc. +/// // Note you can use `register_server_functions_with_context` +/// // to inject the context into server functions running outside +/// // of an SSR render context. +/// .fallback(get(render_handler) +/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app)) +/// ) +/// .into_make_service(); +/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); +/// axum::serve(listener, router).await.unwrap(); +/// } +/// ``` +pub async fn render_handler( + State(state): State, + request: Request, +) -> impl IntoResponse { + let cfg = &state.config; + let ssr_state = state.ssr_state(); + let build_virtual_dom = { + let build_virtual_dom = state.build_virtual_dom.clone(); + let context_providers = state.config.context_providers.clone(); + move || { + let mut vdom = build_virtual_dom(); + for state in context_providers.as_slice() { + vdom.insert_any_root_context(state()); + } + vdom + } + }; + + let (parts, _) = request.into_parts(); + let url = parts + .uri + .path_and_query() + .ok_or(StatusCode::BAD_REQUEST)? + .to_string(); + let parts: Arc> = + Arc::new(parking_lot::RwLock::new(parts)); + // Create the server context with info from the request + let server_context = DioxusServerContext::from_shared_parts(parts.clone()); + // Provide additional context from the render state + add_server_context(&server_context, &state.config.context_providers); + + match ssr_state + .render(url, cfg, build_virtual_dom, &server_context) + .await + { + Ok((freshness, rx)) => { + let mut response = axum::response::Html::from(Body::from_stream(rx)).into_response(); + freshness.write(response.headers_mut()); + server_context.send_response(&mut response); + Result::, StatusCode>::Ok(response) + } + Err(SSRError::Incremental(e)) => { + tracing::error!("Failed to render page: {}", e); + Ok(report_err(e).into_response()) + } + Err(SSRError::Routing(e)) => { + tracing::trace!("Page not found: {}", e); + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("Page not found")) + .unwrap()) + } + } +} + +fn report_err(e: E) -> Response { + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(body::Body::new(format!("Error: {}", e))) + .unwrap() +} diff --git a/packages/subsecond/subsecond-cli-support/src/lib.rs b/packages/subsecond/subsecond-cli-support/src/lib.rs index 6dbb50d391..e552b7b799 100644 --- a/packages/subsecond/subsecond-cli-support/src/lib.rs +++ b/packages/subsecond/subsecond-cli-support/src/lib.rs @@ -94,10 +94,10 @@ pub fn create_jump_table( } let aslr_reference = old_name_to_addr - .get("aslr_reference") + .get("_aslr_reference") .unwrap_or_else(|| { old_name_to_addr - .get("_aslr_reference") + .get("aslr_reference") .expect("failed to find aslr_reference") }) .clone(); @@ -306,7 +306,7 @@ pub fn resolve_undefined( let abs_addr = sym.address() + aslr_offset; - tracing::debug!("Defining: {:?}", name); + tracing::trace!("Defining: {:?} -> {:?}", name, sym.kind()); if sym.kind() == SymbolKind::Text { let jump_code = match triple.architecture { @@ -362,16 +362,30 @@ pub fn resolve_undefined( flags: object::SymbolFlags::None, }); } else { - obj.add_symbol(Symbol { - name: name.as_bytes()[name_offset..].to_vec(), - value: abs_addr, - size: 0, - scope: SymbolScope::Linkage, - kind: sym.kind(), - weak: sym.is_weak(), - section: SymbolSection::Absolute, - flags: object::SymbolFlags::None, - }); + // It's likely a static + if sym.kind() == SymbolKind::Unknown { + obj.add_symbol(Symbol { + name: name.as_bytes()[name_offset..].to_vec(), + value: abs_addr, + size: 0, + scope: SymbolScope::Dynamic, + kind: SymbolKind::Data, + weak: false, + section: SymbolSection::Absolute, + flags: object::SymbolFlags::None, + }); + } else { + obj.add_symbol(Symbol { + name: name.as_bytes()[name_offset..].to_vec(), + value: abs_addr, + size: 0, + scope: SymbolScope::Linkage, + kind: sym.kind(), + weak: sym.is_weak(), + section: SymbolSection::Absolute, + flags: object::SymbolFlags::None, + }); + } } } diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index 871fbf35ab..11a7e7fe76 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -366,6 +366,17 @@ pub fn register_handler(handler: Arc) { /// This function will load the library and thus allocates. In cannot be used when the program is /// stopped (ie in a signal handler). pub unsafe fn apply_patch(mut table: JumpTable) { + // As a form of integrity checking, we use the ASLR reference from the incoming jump table to assert that it is intended to patch us + // #[cfg(any(unix, windows))] + // if table.aslr_reference != aslr_reference() as u64 { + // println!( + // "ASLR reference mismatch: {} != {}", + // table.aslr_reference, + // aslr_reference() + // ); + // return; + // } + // On non-wasm platforms we can just use libloading and the known aslr offsets to load the library #[cfg(any(unix, windows))] { @@ -374,7 +385,18 @@ pub unsafe fn apply_patch(mut table: JumpTable) { let lib = Box::leak(Box::new(android_memmap_dlopen(&table.lib))); #[cfg(not(target_os = "android"))] - let lib = Box::leak(Box::new(libloading::Library::new(&table.lib).unwrap())); + let lib = Box::leak(Box::new({ + match libloading::Library::new(&table.lib) { + Ok(lib) => { + println!("Loaded library success!: {:?}", &table.lib); + lib + } + err => { + eprintln!("Failed to load library: {:?}", err); + return; + } + } + })); // Use the `aslr_offset` symbol as a sentinel for the current executable. This is basically a // cross-platform version of `__mh_execute_header` on macOS that sets a reference point for the @@ -409,7 +431,7 @@ pub unsafe fn apply_patch(mut table: JumpTable) { }) .collect(); - // commit_patch(table); + commit_patch(table); }; // On wasm, we need to download the module, compile it, and then run it. diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index e032eae2dc..165c45cd6c 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -167,6 +167,7 @@ fn make_ws( } else { ws_tx.send_with_str( &serde_json::to_string(&ClientMsg::Initialize { + build_id: dioxus_devtools::build_id(), aslr_reference: subsecond::aslr_reference() as _, }) .unwrap(), From 96dbe1ec88885459a431492782b7122bae001c8b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 14 Apr 2025 12:33:07 -0700 Subject: [PATCH 114/301] use rustc wrapper --- packages/cli/src/build/request.rs | 149 ++++++----- packages/cli/src/main.rs | 6 + packages/cli/src/rustcwrapper.rs | 53 ++++ .../subsecond/subsecond-harness/Cargo.toml | 4 +- .../subsecond-harness/src/dioxus_demo.rs | 231 +++++++++++------- packages/subsecond/subsecond/src/lib.rs | 9 +- 6 files changed, 303 insertions(+), 149 deletions(-) create mode 100644 packages/cli/src/rustcwrapper.rs diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index b653187dcf..7f899fabc5 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -229,8 +229,8 @@ use super::{AndroidTools, BuildContext, BuildId}; use crate::{ - wasm_bindgen::WasmBindgen, BuildArgs, DioxusConfig, Error, LinkAction, Platform, ProgressTx, - Result, TraceSrc, WasmOptConfig, Workspace, + rustcwrapper::RustcArgs, wasm_bindgen::WasmBindgen, BuildArgs, DioxusConfig, Error, LinkAction, + Platform, ProgressTx, Result, TraceSrc, WasmOptConfig, Workspace, }; use anyhow::Context; use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV}; @@ -247,6 +247,7 @@ use std::{ path::{Path, PathBuf}, pin::Pin, process::Stdio, + str::FromStr, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -337,7 +338,7 @@ pub enum BuildMode { /// A "thin" build generated with `rustc` directly and dx as a custom linker Thin { - direct_rustc: Vec, + direct_rustc: RustcArgs, changed_files: Vec, aslr_reference: u64, }, @@ -351,7 +352,7 @@ pub enum BuildMode { pub struct BuildArtifacts { pub(crate) platform: Platform, pub(crate) exe: PathBuf, - pub(crate) direct_rustc: Vec, + pub(crate) direct_rustc: RustcArgs, pub(crate) time_start: SystemTime, pub(crate) time_end: SystemTime, pub(crate) assets: AssetManifest, @@ -632,7 +633,6 @@ impl BuildRequest { let mut stderr = stderr.lines(); let mut units_compiled = 0; let mut emitting_error = false; - let mut direct_rustc = Vec::new(); loop { use cargo_metadata::Message; @@ -650,17 +650,17 @@ impl BuildRequest { match message { Message::BuildScriptExecuted(_) => units_compiled += 1, Message::TextLine(line) => { - // Try to extract the direct rustc args from the output - if line.trim().starts_with("Running ") { - // trim everyting but the contents between the quotes - let args = line - .trim() - .trim_start_matches("Running `") - .trim_end_matches('`'); - - // Parse these as shell words so we can get the direct rustc args - direct_rustc = shell_words::split(args).unwrap(); - } + // // Try to extract the direct rustc args from the output + // if line.trim().starts_with("Running ") { + // // trim everyting but the contents between the quotes + // let args = line + // .trim() + // .trim_start_matches("Running `") + // .trim_end_matches('`'); + + // // Parse these as shell words so we can get the direct rustc args + // direct_rustc = shell_words::split(args).unwrap(); + // } #[derive(Deserialize)] struct RustcArtifact { @@ -722,6 +722,13 @@ impl BuildRequest { let mode = ctx.mode.clone(); tracing::debug!("Build completed successfully - output location: {:?}", exe); + let mut direct_rustc = RustcArgs::default(); + + if let Ok(res) = std::fs::read_to_string(self.rustc_wrapper_args_file()) { + let res: crate::rustcwrapper::RustcArgs = serde_json::from_str(&res).unwrap(); + direct_rustc = res; + } + Ok(BuildArtifacts { platform: self.platform, exe, @@ -733,6 +740,13 @@ impl BuildRequest { }) } + fn rustc_wrapper_args_file(&self) -> PathBuf { + // self.platform_dir() + PathBuf::from_str("/Users/jonkelley/Development/dioxus/packages/subsecond/data/") + .unwrap() + .join(format!("rustc_args-{}.json", self.triple)) + } + /// Traverse the target directory and collect all assets from the incremental cache /// /// This uses "known paths" that have stayed relatively stable during cargo's lifetime. @@ -1209,44 +1223,61 @@ impl BuildRequest { fields(dx_src = ?TraceSrc::Build) )] fn build_command(&self, ctx: &BuildContext) -> Result { - // Prefer using the direct rustc if we have it - // if let BuildMode::Thin { direct_rustc, .. } = &ctx.mode { - // tracing::debug!("Using direct rustc: {:?}", direct_rustc); - // if !direct_rustc.is_empty() { - // let mut cmd = Command::new("cargo"); - // cmd.args(["rustc"]); - // cmd.envs(self.env_vars(ctx)?); - // cmd.current_dir(self.workspace_dir()); - // cmd.arg(format!( - // "-Clinker={}", - // dunce::canonicalize(std::env::current_exe().unwrap()) - // .unwrap() - // .display() - // )); - // // let mut cmd = Command::new(direct_rustc[0].clone()); - // // cmd.args(direct_rustc[1..].iter()); - // // cmd.envs(self.env_vars(ctx)?); - // // cmd.current_dir(self.workspace_dir()); - // // cmd.arg(format!( - // // "-Clinker={}", - // // dunce::canonicalize(std::env::current_exe().unwrap()) - // // .unwrap() - // // .display() - // // )); - // return Ok(cmd); - // } - // } + match &ctx.mode { + BuildMode::Thin { direct_rustc, .. } => { + let mut cmd = Command::new("rustc"); + cmd.env_clear(); + // let mut cmd = Command::new(direct_rustc.args[0].clone()); + cmd.args(direct_rustc.args[1..].iter()); + cmd.envs(direct_rustc.envs.iter()); + // cmd.envs(direct_rustc.envs.iter().filter(|(k, v)| { + // // (k.as_str() != "_") + // (k.as_str() != "RUSTC_WORKSPACE_WRAPPER") + // || (k.as_str() != "RUSTC_WRAPPER") + // || (k.as_str() != "DX_RUSTC") + // })); + cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); + cmd.env_remove("RUSTC_WRAPPER"); + cmd.env_remove("DX_RUSTC"); + cmd.current_dir(self.workspace_dir()); + cmd.arg(format!( + "-Clinker={}", + dunce::canonicalize(std::env::current_exe().unwrap()) + .unwrap() + .display() + )); + cmd.envs(self.env_vars(ctx)?); + tracing::debug!("Using rustc wrapper args: {:#?}", cmd); + Ok(cmd) + } - // Otherwise build up the command using cargo rustc - let mut cmd = Command::new("cargo"); - cmd.arg("rustc") - .current_dir(self.crate_dir()) - .arg("--message-format") - .arg("json-diagnostic-rendered-ansi") - .args(self.build_arguments(ctx)) - .envs(self.env_vars(ctx)?); + // Out of caution, use cargo rustc instead of dx as a fallback + _ => { + let mut cmd = Command::new("cargo"); + cmd.arg("rustc") + .current_dir(self.crate_dir()) + .arg("--message-format") + .arg("json-diagnostic-rendered-ansi") + .args(self.build_arguments(ctx)) + .envs(self.env_vars(ctx)?); + + if ctx.mode == BuildMode::Fat { + cmd.env( + crate::rustcwrapper::RUSTC_WRAPPER_ENV_VAR, + self.rustc_wrapper_args_file(), + ); + cmd.env( + "RUSTC_WRAPPER", + dunce::canonicalize(std::env::current_exe().unwrap()) + .unwrap() + .display() + .to_string(), + ); + } - Ok(cmd) + Ok(cmd) + } + } } /// Create a list of arguments for cargo builds @@ -1404,8 +1435,6 @@ impl BuildRequest { // } // } - tracing::debug!(dx_src = ?TraceSrc::Build, "cargo args: {:?}", cargo_args); - cargo_args } @@ -2512,10 +2541,10 @@ impl BuildRequest { // Lift the internal functions to exports if ctx.mode == BuildMode::Fat { - let unprocessed = std::fs::read(prebindgen)?; + let unprocessed = std::fs::read(&prebindgen)?; let all_exported_bytes = subsecond_cli_support::prepare_wasm_base_module(&unprocessed).unwrap(); - std::fs::write(&prebindgen, all_exported_bytes)?; + std::fs::write(&rustc_exe, all_exported_bytes)?; } // Prepare our configuration @@ -2658,10 +2687,10 @@ impl BuildRequest { } // Make sure to optimize the main wasm file if requested or if bundle splitting - if should_bundle_split || self.release { - ctx.status_optimizing_wasm(); - wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; - } + // if should_bundle_split || self.release { + // ctx.status_optimizing_wasm(); + // wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; + // } // Make sure to register the main wasm file with the asset system assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 7636e9e176..19aa0a8c8b 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -13,6 +13,7 @@ mod fastfs; mod logging; mod metadata; mod platform; +mod rustcwrapper; mod serve; mod settings; mod wasm_bindgen; @@ -32,6 +33,11 @@ pub(crate) use workspace::*; #[tokio::main] async fn main() { + // The CLI uses dx as a rustcwrapper in some instances (like binary patching) + if rustcwrapper::is_rustc() { + return rustcwrapper::run_rustc().await; + } + // If we're being ran as a linker (likely from ourselves), we want to act as a linker instead. if let Some(link_action) = link::LinkAction::from_env() { return link_action.run().await.unwrap(); diff --git a/packages/cli/src/rustcwrapper.rs b/packages/cli/src/rustcwrapper.rs new file mode 100644 index 0000000000..43bf564e1a --- /dev/null +++ b/packages/cli/src/rustcwrapper.rs @@ -0,0 +1,53 @@ +use std::{collections::HashMap, path::PathBuf, process::ExitCode}; + +use crate::{Platform, Result}; + +/// The environment variable indicating where the args file is located. +/// +/// When `dx-rustc` runs, it writes its arguments to this file. +pub const RUSTC_WRAPPER_ENV_VAR: &str = "DX_RUSTC"; + +pub fn is_rustc() -> bool { + std::env::var(RUSTC_WRAPPER_ENV_VAR).is_ok() +} + +#[derive(Default, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct RustcArgs { + pub args: Vec, + pub envs: HashMap, +} + +/// Run rustc directly, but output the result to a file. +/// +/// https://doc.rust-lang.org/cargo/reference/config.html#buildrustc +pub async fn run_rustc() { + let var_file: PathBuf = std::env::var(RUSTC_WRAPPER_ENV_VAR) + .expect("DX_RUSTC not set") + .into(); + + let rustc_args = RustcArgs { + envs: std::env::vars() + .map(|(k, v)| (k, v)) + .collect::>(), + args: std::env::args().skip(1).collect::>(), + }; + + std::fs::create_dir_all(var_file.parent().expect("Failed to get parent dir")) + .expect("Failed to create parent dir"); + std::fs::write( + &var_file, + serde_json::to_string(&rustc_args).expect("Failed to serialize rustc args"), + ) + .expect("Failed to write rustc args to file"); + + // Run the actual rustc command + let mut cmd = std::process::Command::new("rustc"); + cmd.args(rustc_args.args.iter().skip(1)); + cmd.envs(rustc_args.envs); + cmd.stdout(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); + cmd.current_dir(std::env::current_dir().expect("Failed to get current dir")); + + // Propagate the exit code + std::process::exit(cmd.status().unwrap().code().unwrap()) +} diff --git a/packages/subsecond/subsecond-harness/Cargo.toml b/packages/subsecond/subsecond-harness/Cargo.toml index c260c3c4cc..9cc507dbc3 100644 --- a/packages/subsecond/subsecond-harness/Cargo.toml +++ b/packages/subsecond/subsecond-harness/Cargo.toml @@ -18,7 +18,9 @@ tungstenite = { version = "0.23.0" } tokio = { workspace = true, features = ["full"] } [features] -default = [ ] +default = [] +fullstack = [ "dioxus/fullstack"] desktop = [ "dioxus/desktop" ] mobile = [ "dioxus/mobile" ] web = [ "dioxus/web" ] +server = ["dioxus/server"] diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs index a7c79fee97..ebce62d377 100644 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs @@ -4,103 +4,164 @@ pub fn launch() { dioxus::launch(app); } +// fn app() -> Element { +// let mut count = use_signal(|| 0); + +// rsx! { +// div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", +// h1 { "Apple: {count} ???" } +// button { onclick: move |_| count += 1, "Incr" } +// button { onclick: move |_| count -= 1, "Decr" } +// img { +// width: "300px", +// src: "https://rustacean.net/assets/rustacean-flat-happy.png", +// } +// } +// div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", +// div { style: "background-color: red", +// for x in 0..1 { +// Child { id: x + 1, opt: "List entry", color: "gri" } +// } +// } +// div { style: "background-color: orange", +// for x in 0..1 { +// Child { id: x + 1, opt: "List entry", color: "blue" } +// } +// } +// div { style: "background-color: yellow", +// for x in 0..1 { +// Child { id: x + 1, opt: "List entry", color: "yellow" } +// } +// } +// div { style: "background-color: green", +// for x in 0..1 { +// Child { id: x + 10, opt: "List entry", color: "orange" } +// } +// } +// div { style: "background-color: blue", +// for x in 0..1 { +// Child { id: x + 10, opt: "List entry", color: "bluebleu" } +// } +// } +// div { style: "background-color: indigo", +// for x in 0..1 { +// Child { id: x + 10, opt: "List entry", color: "magentaaa" } +// } +// } +// } +// } +// } + +// #[component] +// fn Child(id: u32, opt: String, color: String) -> Element { +// let mut count = use_signal(|| 0); + +// rsx! { +// div { +// h3 { "Chil!!!!!!!!!! {id} - {opt} - {color} - {color} - {color}" } +// p { "count: {count}" } +// button { +// onclick: move |_| { +// count += id; +// }, +// "Increment Count" +// } +// } +// } +// } +// #[component] +// fn Child2(id: u32, opt: String) -> Element { +// rsx! { +// div { "oh lordy!" } +// div { "Hello ?? child2s: {id} - {opt} ?" } +// } +// } + +// #[component] +// fn Child3(id: u32, opt: String) -> Element { +// rsx! { +// div { "Hello ?? child: {id} - {opt} ?" } +// } +// } + +// #[component] +// fn Child4(id: u32, opt: String) -> Element { +// rsx! { +// div { "Hello ?? child: {id} - {opt} ?" } +// div { "Hello ?? child: {id} - {opt} ?" } +// div { "Hello ?? child: {id} - {opt} ?" } +// } +// } + +// #[component] +// fn ZoomComponent() -> Element { +// // use dioxus::desktop::window; +// // button { onclick: move |_| window().set_zoom_level(1.0), "Zoom 1x" } +// // button { onclick: move |_| window().set_zoom_level(1.5), "Zoom 1.5x" } +// // button { onclick: move |_| window().set_zoom_level(2.0), "Zoom 2x" } +// // button { onclick: move |_| window().set_zoom_level(3.0), "Zoom 3x" } +// rsx! { +// div { "Zoom me!" } +// } +// } + +// fn app() -> Element { +// let mut items: Signal> = use_signal(|| vec![]); + +// rsx! { +// div { +// h1 { "Build a todo list!" } +// h1 { "Build a todo list!" } +// h1 { "Build a todo list!" } +// button { +// onclick: move |_| async move { +// // let res = request_item(123).await.unwrap(); +// items.write().push("Item".to_string()); +// }, +// "Add Item" +// } + +// for item in items.iter() { +// li { "{item}" } +// } +// } +// } +// } + fn app() -> Element { - let mut count = use_signal(|| 0); + let mut items: Signal> = use_signal(|| vec![]); rsx! { - div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", - h1 { "Apple: {count} ???" } - button { onclick: move |_| count += 1, "Incr" } - button { onclick: move |_| count -= 1, "Decr" } - img { - width: "300px", - src: "https://rustacean.net/assets/rustacean-flat-happy.png", - } - } - div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", - div { style: "background-color: red", - for x in 0..1 { - Child { id: x + 1, opt: "List entry", color: "gri" } - } - } - div { style: "background-color: orange", - for x in 0..1 { - Child { id: x + 1, opt: "List entry", color: "blue" } - } - } - div { style: "background-color: yellow", - for x in 0..1 { - Child { id: x + 1, opt: "List entry", color: "yellow" } - } - } - div { style: "background-color: green", - for x in 0..1 { - Child { id: x + 10, opt: "List entry", color: "orange" } - } - } - div { style: "background-color: blue", - for x in 0..1 { - Child { id: x + 10, opt: "List entry", color: "bluebleu" } - } - } - div { style: "background-color: indigo", - for x in 0..1 { - Child { id: x + 10, opt: "List entry", color: "magentaaa" } - } + div { + h1 { "Build a todo list!" } + button { + onclick: move |_| async move { + // let res = request_item(123).await.unwrap(); + let res = "Item".to_string(); + items.write().push(res); + }, + "Add Item" } - } - } -} -#[component] -fn Child(id: u32, opt: String, color: String) -> Element { - let mut count = use_signal(|| 0); - rsx! { - div { - h3 { "Chil!!!!!!!!!! {id} - {opt} - {color} - {color} - {color}" } - p { "count: {count}" } button { onclick: move |_| { - count += id; + items.write().clear(); }, - "Increment Count" + "Clear Items" } - } - } -} -#[component] -fn Child2(id: u32, opt: String) -> Element { - rsx! { - div { "oh lordy!" } - div { "Hello ?? child2s: {id} - {opt} ?" } - } -} -#[component] -fn Child3(id: u32, opt: String) -> Element { - rsx! { - div { "Hello ?? child: {id} - {opt} ?" } - } -} -#[component] -fn Child4(id: u32, opt: String) -> Element { - rsx! { - div { "Hello ?? child: {id} - {opt} ?" } - div { "Hello ?? child: {id} - {opt} ?" } - div { "Hello ?? child: {id} - {opt} ?" } + for item in items.iter() { + li { "{item}" } + } + } } } -#[component] -fn ZoomComponent() -> Element { - // use dioxus::desktop::window; - // button { onclick: move |_| window().set_zoom_level(1.0), "Zoom 1x" } - // button { onclick: move |_| window().set_zoom_level(1.5), "Zoom 1.5x" } - // button { onclick: move |_| window().set_zoom_level(2.0), "Zoom 2x" } - // button { onclick: move |_| window().set_zoom_level(3.0), "Zoom 3x" } - rsx! { - div { "Zoom me!" } - } -} +// #[server(endpoint = "request_item")] +// pub async fn request_item(val: i32) -> Result { +// dioxus::subsecond::call(|| { +// Ok("hotpatchy frontend frontend frontend frontend frontend!!!".to_string()) +// }) +// } diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index 11a7e7fe76..c4c96c05da 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -451,10 +451,13 @@ pub unsafe fn apply_patch(mut table: JumpTable) { let exports: Object = wasm_bindgen::exports().unchecked_into(); let buffer: ArrayBuffer = memory.buffer().unchecked_into(); + let path = table.lib.to_str().unwrap(); + if !path.ends_with(".wasm") { + return; + } + // Start the fetch of the module - let response = web_sys::window() - .unwrap_throw() - .fetch_with_str(&table.lib.to_str().unwrap()); + let response = web_sys::window().unwrap_throw().fetch_with_str(&path); // Wait for the fetch to complete - we need the wasm module size in bytes to reserve in the memory let response: web_sys::Response = JsFuture::from(response).await.unwrap().unchecked_into(); From 2ef30a22a3e73385930155449df6e040a4d09c8f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 12:46:25 -0700 Subject: [PATCH 115/301] wip: passing assets to jumptable --- examples/readme.rs | 20 +++ packages/cli-opt/src/lib.rs | 3 +- packages/manganis/manganis-core/src/entry.rs | 26 ++++ packages/manganis/manganis-core/src/fnmeta.rs | 0 packages/manganis/manganis-core/src/lib.rs | 6 + .../manganis/manganis-core/src/options.rs | 4 + .../subsecond-cli-support/src/lib.rs | 118 +++++++++++++++++- packages/subsecond/subsecond-cli/src/main.rs | 6 + .../subsecond-harness/assets/test.css | 12 ++ .../subsecond-harness/src/dioxus_demo.rs | 86 +++++++------ 10 files changed, 240 insertions(+), 41 deletions(-) create mode 100644 packages/manganis/manganis-core/src/entry.rs create mode 100644 packages/manganis/manganis-core/src/fnmeta.rs create mode 100644 packages/subsecond/subsecond-harness/assets/test.css diff --git a/examples/readme.rs b/examples/readme.rs index 1c36c65c00..a6be1c30cb 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -14,9 +14,29 @@ fn main() { fn app() -> Element { let mut count = use_signal(|| 0); + does_thing(); + match DOES_THING_META { + Entry::ServerFn { f } => f(), + Entry::Asset => {} + } + rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } } } + +#[link_section = concat!("__TEXT,__manganis")] +pub fn does_thing() { + println!("Hello from the dioxus example!"); +} + +#[link_section = concat!("__DATA,__manganis")] +pub static DOES_THING_META: Entry = Entry::ServerFn { f: does_thing }; + +#[repr(C, u8)] +enum Entry { + ServerFn { f: fn() }, + Asset, +} diff --git a/packages/cli-opt/src/lib.rs b/packages/cli-opt/src/lib.rs index 97c934a747..5823add405 100644 --- a/packages/cli-opt/src/lib.rs +++ b/packages/cli-opt/src/lib.rs @@ -102,7 +102,8 @@ impl AssetManifest { continue; }; - // Check if the link section matches the asset section for one of the platforms we support. This may not be the current platform if the user is cross compiling + // Check if the link section matches the asset section for one of the platforms we support. + // This may not be the current platform if the user is cross compiling let matches = LinkSection::ALL .iter() .any(|x| x.link_section == section_name); diff --git a/packages/manganis/manganis-core/src/entry.rs b/packages/manganis/manganis-core/src/entry.rs new file mode 100644 index 0000000000..fb33927402 --- /dev/null +++ b/packages/manganis/manganis-core/src/entry.rs @@ -0,0 +1,26 @@ +use const_serialize::SerializeConst; + +use crate::BundledAsset; + +#[derive( + Debug, + PartialEq, + PartialOrd, + Clone, + Copy, + Hash, + SerializeConst, + serde::Serialize, + serde::Deserialize, +)] +#[repr(C, u8)] +#[non_exhaustive] +pub enum ManganisEntry { + /// An asset (ie a file) that should be copied by the bundler with some options. + Asset(BundledAsset), + + /// A function that exports some additional metadata + /// + /// The function will end up in its own section + Metadata(), +} diff --git a/packages/manganis/manganis-core/src/fnmeta.rs b/packages/manganis/manganis-core/src/fnmeta.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/manganis/manganis-core/src/lib.rs b/packages/manganis/manganis-core/src/lib.rs index 52e6ac80b2..f7daced1f3 100644 --- a/packages/manganis/manganis-core/src/lib.rs +++ b/packages/manganis/manganis-core/src/lib.rs @@ -19,3 +19,9 @@ pub use asset::*; pub mod linker; pub mod hash; + +mod fnmeta; +pub use fnmeta::*; + +pub mod entry; +pub use entry::*; diff --git a/packages/manganis/manganis-core/src/options.rs b/packages/manganis/manganis-core/src/options.rs index 714c1f9007..8136be96f9 100644 --- a/packages/manganis/manganis-core/src/options.rs +++ b/packages/manganis/manganis-core/src/options.rs @@ -19,12 +19,16 @@ use crate::{CssAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptio pub enum AssetOptions { /// An image asset Image(ImageAssetOptions), + /// A folder asset Folder(FolderAssetOptions), + /// A css asset Css(CssAssetOptions), + /// A javascript asset Js(JsAssetOptions), + /// An unknown asset Unknown, } diff --git a/packages/subsecond/subsecond-cli-support/src/lib.rs b/packages/subsecond/subsecond-cli-support/src/lib.rs index e552b7b799..11f35802d9 100644 --- a/packages/subsecond/subsecond-cli-support/src/lib.rs +++ b/packages/subsecond/subsecond-cli-support/src/lib.rs @@ -4,7 +4,7 @@ use memmap::{Mmap, MmapOptions}; use object::{ macho::{self, ARM64_RELOC_UNSIGNED, MH_TWOLEVEL}, read::File, - write::{MachOBuildVersion, Relocation, StandardSection, Symbol, SymbolSection}, + write::{MachOBuildVersion, Relocation, SectionId, StandardSection, Symbol, SymbolSection}, Architecture, BinaryFormat, Endianness, Object, ObjectSection, ObjectSymbol, ObjectSymbolTable, RelocationFlags, RelocationTarget, SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, @@ -368,7 +368,7 @@ pub fn resolve_undefined( name: name.as_bytes()[name_offset..].to_vec(), value: abs_addr, size: 0, - scope: SymbolScope::Dynamic, + scope: SymbolScope::Linkage, kind: SymbolKind::Data, weak: false, section: SymbolSection::Absolute, @@ -389,6 +389,120 @@ pub fn resolve_undefined( } } + // The loader host might want to know the address of various sections + // Let's add some symbols in the form of __SECTION_START_{SECTION_NAME} and __SECTION_END_{SECTION_NAME} + // such that dlsym can be used to find them. + // + // This will also be used by the program loader to identify the ASLR slide + for in_section in source.sections() { + // tracing::info!("Defining section header: {:?}", section); + // let sym = obj.section_symbol(section_id); + + let Ok(name) = in_section.name_bytes() else { + tracing::error!("Section has no name: {:?}", in_section); + continue; + }; + + if name != b"manganis" { + continue; + } + + let mut start = None; + for s in source.symbols() { + if s.section_index() == Some(in_section.index()) { + tracing::info!("Reading symbol header: {:?}", s); + if start.is_none() { + start = Some(s); + } + } + } + + // if let Some(s) = start { + // // import the symbol + // let id = obj.add_symbol(Symbol { + // name: format!("__SECTION_START_{}", s.name().unwrap()) + // .as_bytes() + // .to_vec(), + // value: 0, + // size: 0, + // kind: s.kind(), + // scope: SymbolScope::Dynamic, + // weak: false, + // section: SymbolSection::Section(in_section.index()), + // flags: object::SymbolFlags::None, + // }); + + // // Define a new symbol + + // // obj.add_symbol(Symbol { + // // name: format!("__SECTION_START_{}", s.name().unwrap_or_default()) + // // .as_bytes() + // // .to_vec(), + // // value: 0, + // // size: 0, + // // kind: (), + // // scope: (), + // // weak: (), + // // section: (), + // // flags: (), + // // }); + // } + + let kind = if in_section.kind() == SectionKind::Unknown { + SectionKind::Data + } else { + in_section.kind() + }; + + let section_id = obj.add_section( + in_section + .segment_name() + .unwrap() + .unwrap_or("") + .as_bytes() + .to_vec(), + name.to_vec(), + kind, + ); + let out_section = obj.section_mut(section_id); + out_section.flags = in_section.flags(); + + if out_section.is_bss() { + } else { + // obj.set_section_data(section_id, &[0_u8, 1, 2, 3, 4, 5, 6, 7], 4); + tracing::info!("Defining section header: {:?}", in_section); + let sym = obj.add_symbol(Symbol { + name: format!("__SECTION_START_{}", in_section.name().unwrap_or_default()) + .as_bytes() + .to_vec(), + value: 0, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: SymbolSection::Section(section_id), + flags: object::SymbolFlags::None, + }); + } + } + + // // "__DATA,manganis,regular,no_dead_strip".as_bytes().to_vec(), + // let sect = obj.add_section( + // "__DATA".as_bytes().to_vec(), + // "manganis".as_bytes().to_vec(), + // SectionKind::Data, + // ); + // let sym = obj.add_symbol(Symbol { + // name: format!("__SECTION_START_MANGANIS").as_bytes().to_vec(), + // value: 0, + // size: 0, + // kind: SymbolKind::Data, + // scope: SymbolScope::Dynamic, + // weak: false, + // section: SymbolSection::Section(sect), + // flags: object::SymbolFlags::None, + // }); + // Write the object to a file let bytes = obj.write()?; Ok(bytes) diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs index dc430eaf8a..a62f368452 100644 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ b/packages/subsecond/subsecond-cli/src/main.rs @@ -301,6 +301,10 @@ async fn fast_build( std::env::current_exe().unwrap().display() )) .env("HOTRELOAD_LINK", "patch") + .env( + "CARGO_MANIFEST_DIR", + workspace_dir().join("packages/subsecond/subsecond-harness"), + ) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .current_dir(workspace_dir()) @@ -364,6 +368,8 @@ async fn fast_build( .arg("-Wl,-dylib") .arg("-arch") .arg("arm64") + .arg("-Wl,-all_load") + // .arg("-Wl,-exported_symbol,__SECTION_START_MANGANIS") .arg("-o") .arg(&output_location) .stdout(Stdio::piped()) diff --git a/packages/subsecond/subsecond-harness/assets/test.css b/packages/subsecond/subsecond-harness/assets/test.css new file mode 100644 index 0000000000..c256a7964b --- /dev/null +++ b/packages/subsecond/subsecond-harness/assets/test.css @@ -0,0 +1,12 @@ +#external-links { + display: flex; + flex-direction: column; +} + +#nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background-color: #f4f4f4; +} diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs index ebce62d377..f71190fd6f 100644 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs @@ -4,6 +4,54 @@ pub fn launch() { dioxus::launch(app); } +static CSS1: Asset = asset!("/assets/test.css"); +static CSS2: Asset = asset!("/assets/test.css"); +static CSS3: Asset = asset!("/assets/test.css"); + +fn app() -> Element { + let mut items: Signal> = use_signal(|| vec![]); + + rsx! { + div { + link { href: CSS1, rel: "stylesheet" } + link { href: CSS2, rel: "stylesheet" } + link { href: CSS3, rel: "stylesheet" } + h1 { "Build a todo list!" } + h1 { "Build a todo list!" } + h1 { "Build a todo list!" } + h1 { "Build a todo list!" } + button { + onclick: move |_| async move { + // let res = request_item(123).await.unwrap(); + let res = "Item".to_string(); + items.write().push(res); + }, + "Add Item" + } + + + button { + onclick: move |_| { + items.write().clear(); + }, + "Clear Items" + } + + + for item in items.iter() { + li { "{item}" } + } + } + } +} + +// #[server(endpoint = "request_item")] +// pub async fn request_item(val: i32) -> Result { +// dioxus::subsecond::call(|| { +// Ok("hotpatchy frontend frontend frontend frontend frontend!!!".to_string()) +// }) +// } + // fn app() -> Element { // let mut count = use_signal(|| 0); @@ -127,41 +175,3 @@ pub fn launch() { // } // } // } - -fn app() -> Element { - let mut items: Signal> = use_signal(|| vec![]); - - rsx! { - div { - h1 { "Build a todo list!" } - button { - onclick: move |_| async move { - // let res = request_item(123).await.unwrap(); - let res = "Item".to_string(); - items.write().push(res); - }, - "Add Item" - } - - - button { - onclick: move |_| { - items.write().clear(); - }, - "Clear Items" - } - - - for item in items.iter() { - li { "{item}" } - } - } - } -} - -// #[server(endpoint = "request_item")] -// pub async fn request_item(val: i32) -> Result { -// dioxus::subsecond::call(|| { -// Ok("hotpatchy frontend frontend frontend frontend frontend!!!".to_string()) -// }) -// } From c919aa35efb8bc408767f3490dbc221f90ec16aa Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 15:09:40 -0700 Subject: [PATCH 116/301] migrate project-like examples --- .../fullstack-auth/.gitignore | 0 .../fullstack-auth/Cargo.toml | 0 .../fullstack-auth/src/auth.rs | 0 .../fullstack-auth/src/main.rs | 0 .../fullstack-desktop/.gitignore | 0 .../fullstack-desktop/Cargo.toml | 0 .../fullstack-desktop/src/main.rs | 0 .../fullstack-hello-world/.gitignore | 0 .../fullstack-hello-world/Cargo.toml | 0 .../fullstack-hello-world/assets/hello.css | 0 .../fullstack-hello-world/src/main.rs | 0 .../fullstack-router/.gitignore | 0 .../fullstack-router/Cargo.toml | 0 .../fullstack-router/src/main.rs | 0 .../fullstack-streaming/.gitignore | 0 .../fullstack-streaming/Cargo.toml | 0 .../fullstack-streaming/src/main.rs | 0 {examples => example-projects}/pwa/Cargo.toml | 0 {examples => example-projects}/pwa/Dioxus.toml | 0 {examples => example-projects}/pwa/LICENSE | 0 {examples => example-projects}/pwa/README.md | 0 {examples => example-projects}/pwa/index.html | 0 .../pwa/public/favicon.ico | Bin .../pwa/public/logo_192.png | Bin .../pwa/public/logo_512.png | Bin .../pwa/public/manifest.json | 0 {examples => example-projects}/pwa/public/sw.js | 0 {examples => example-projects}/pwa/src/main.rs | 0 {examples => example-projects}/tailwind/.gitignore | 0 {examples => example-projects}/tailwind/Cargo.toml | 0 {examples => example-projects}/tailwind/Dioxus.toml | 0 {examples => example-projects}/tailwind/README.md | 0 {examples => example-projects}/tailwind/input.css | 0 .../tailwind/public/tailwind.css | 0 {examples => example-projects}/tailwind/src/main.rs | 0 .../tailwind/tailwind.config.js | 0 36 files changed, 0 insertions(+), 0 deletions(-) rename {examples => example-projects}/fullstack-auth/.gitignore (100%) rename {examples => example-projects}/fullstack-auth/Cargo.toml (100%) rename {examples => example-projects}/fullstack-auth/src/auth.rs (100%) rename {examples => example-projects}/fullstack-auth/src/main.rs (100%) rename {examples => example-projects}/fullstack-desktop/.gitignore (100%) rename {examples => example-projects}/fullstack-desktop/Cargo.toml (100%) rename {examples => example-projects}/fullstack-desktop/src/main.rs (100%) rename {examples => example-projects}/fullstack-hello-world/.gitignore (100%) rename {examples => example-projects}/fullstack-hello-world/Cargo.toml (100%) rename {examples => example-projects}/fullstack-hello-world/assets/hello.css (100%) rename {examples => example-projects}/fullstack-hello-world/src/main.rs (100%) rename {examples => example-projects}/fullstack-router/.gitignore (100%) rename {examples => example-projects}/fullstack-router/Cargo.toml (100%) rename {examples => example-projects}/fullstack-router/src/main.rs (100%) rename {examples => example-projects}/fullstack-streaming/.gitignore (100%) rename {examples => example-projects}/fullstack-streaming/Cargo.toml (100%) rename {examples => example-projects}/fullstack-streaming/src/main.rs (100%) rename {examples => example-projects}/pwa/Cargo.toml (100%) rename {examples => example-projects}/pwa/Dioxus.toml (100%) rename {examples => example-projects}/pwa/LICENSE (100%) rename {examples => example-projects}/pwa/README.md (100%) rename {examples => example-projects}/pwa/index.html (100%) rename {examples => example-projects}/pwa/public/favicon.ico (100%) rename {examples => example-projects}/pwa/public/logo_192.png (100%) rename {examples => example-projects}/pwa/public/logo_512.png (100%) rename {examples => example-projects}/pwa/public/manifest.json (100%) rename {examples => example-projects}/pwa/public/sw.js (100%) rename {examples => example-projects}/pwa/src/main.rs (100%) rename {examples => example-projects}/tailwind/.gitignore (100%) rename {examples => example-projects}/tailwind/Cargo.toml (100%) rename {examples => example-projects}/tailwind/Dioxus.toml (100%) rename {examples => example-projects}/tailwind/README.md (100%) rename {examples => example-projects}/tailwind/input.css (100%) rename {examples => example-projects}/tailwind/public/tailwind.css (100%) rename {examples => example-projects}/tailwind/src/main.rs (100%) rename {examples => example-projects}/tailwind/tailwind.config.js (100%) diff --git a/examples/fullstack-auth/.gitignore b/example-projects/fullstack-auth/.gitignore similarity index 100% rename from examples/fullstack-auth/.gitignore rename to example-projects/fullstack-auth/.gitignore diff --git a/examples/fullstack-auth/Cargo.toml b/example-projects/fullstack-auth/Cargo.toml similarity index 100% rename from examples/fullstack-auth/Cargo.toml rename to example-projects/fullstack-auth/Cargo.toml diff --git a/examples/fullstack-auth/src/auth.rs b/example-projects/fullstack-auth/src/auth.rs similarity index 100% rename from examples/fullstack-auth/src/auth.rs rename to example-projects/fullstack-auth/src/auth.rs diff --git a/examples/fullstack-auth/src/main.rs b/example-projects/fullstack-auth/src/main.rs similarity index 100% rename from examples/fullstack-auth/src/main.rs rename to example-projects/fullstack-auth/src/main.rs diff --git a/examples/fullstack-desktop/.gitignore b/example-projects/fullstack-desktop/.gitignore similarity index 100% rename from examples/fullstack-desktop/.gitignore rename to example-projects/fullstack-desktop/.gitignore diff --git a/examples/fullstack-desktop/Cargo.toml b/example-projects/fullstack-desktop/Cargo.toml similarity index 100% rename from examples/fullstack-desktop/Cargo.toml rename to example-projects/fullstack-desktop/Cargo.toml diff --git a/examples/fullstack-desktop/src/main.rs b/example-projects/fullstack-desktop/src/main.rs similarity index 100% rename from examples/fullstack-desktop/src/main.rs rename to example-projects/fullstack-desktop/src/main.rs diff --git a/examples/fullstack-hello-world/.gitignore b/example-projects/fullstack-hello-world/.gitignore similarity index 100% rename from examples/fullstack-hello-world/.gitignore rename to example-projects/fullstack-hello-world/.gitignore diff --git a/examples/fullstack-hello-world/Cargo.toml b/example-projects/fullstack-hello-world/Cargo.toml similarity index 100% rename from examples/fullstack-hello-world/Cargo.toml rename to example-projects/fullstack-hello-world/Cargo.toml diff --git a/examples/fullstack-hello-world/assets/hello.css b/example-projects/fullstack-hello-world/assets/hello.css similarity index 100% rename from examples/fullstack-hello-world/assets/hello.css rename to example-projects/fullstack-hello-world/assets/hello.css diff --git a/examples/fullstack-hello-world/src/main.rs b/example-projects/fullstack-hello-world/src/main.rs similarity index 100% rename from examples/fullstack-hello-world/src/main.rs rename to example-projects/fullstack-hello-world/src/main.rs diff --git a/examples/fullstack-router/.gitignore b/example-projects/fullstack-router/.gitignore similarity index 100% rename from examples/fullstack-router/.gitignore rename to example-projects/fullstack-router/.gitignore diff --git a/examples/fullstack-router/Cargo.toml b/example-projects/fullstack-router/Cargo.toml similarity index 100% rename from examples/fullstack-router/Cargo.toml rename to example-projects/fullstack-router/Cargo.toml diff --git a/examples/fullstack-router/src/main.rs b/example-projects/fullstack-router/src/main.rs similarity index 100% rename from examples/fullstack-router/src/main.rs rename to example-projects/fullstack-router/src/main.rs diff --git a/examples/fullstack-streaming/.gitignore b/example-projects/fullstack-streaming/.gitignore similarity index 100% rename from examples/fullstack-streaming/.gitignore rename to example-projects/fullstack-streaming/.gitignore diff --git a/examples/fullstack-streaming/Cargo.toml b/example-projects/fullstack-streaming/Cargo.toml similarity index 100% rename from examples/fullstack-streaming/Cargo.toml rename to example-projects/fullstack-streaming/Cargo.toml diff --git a/examples/fullstack-streaming/src/main.rs b/example-projects/fullstack-streaming/src/main.rs similarity index 100% rename from examples/fullstack-streaming/src/main.rs rename to example-projects/fullstack-streaming/src/main.rs diff --git a/examples/pwa/Cargo.toml b/example-projects/pwa/Cargo.toml similarity index 100% rename from examples/pwa/Cargo.toml rename to example-projects/pwa/Cargo.toml diff --git a/examples/pwa/Dioxus.toml b/example-projects/pwa/Dioxus.toml similarity index 100% rename from examples/pwa/Dioxus.toml rename to example-projects/pwa/Dioxus.toml diff --git a/examples/pwa/LICENSE b/example-projects/pwa/LICENSE similarity index 100% rename from examples/pwa/LICENSE rename to example-projects/pwa/LICENSE diff --git a/examples/pwa/README.md b/example-projects/pwa/README.md similarity index 100% rename from examples/pwa/README.md rename to example-projects/pwa/README.md diff --git a/examples/pwa/index.html b/example-projects/pwa/index.html similarity index 100% rename from examples/pwa/index.html rename to example-projects/pwa/index.html diff --git a/examples/pwa/public/favicon.ico b/example-projects/pwa/public/favicon.ico similarity index 100% rename from examples/pwa/public/favicon.ico rename to example-projects/pwa/public/favicon.ico diff --git a/examples/pwa/public/logo_192.png b/example-projects/pwa/public/logo_192.png similarity index 100% rename from examples/pwa/public/logo_192.png rename to example-projects/pwa/public/logo_192.png diff --git a/examples/pwa/public/logo_512.png b/example-projects/pwa/public/logo_512.png similarity index 100% rename from examples/pwa/public/logo_512.png rename to example-projects/pwa/public/logo_512.png diff --git a/examples/pwa/public/manifest.json b/example-projects/pwa/public/manifest.json similarity index 100% rename from examples/pwa/public/manifest.json rename to example-projects/pwa/public/manifest.json diff --git a/examples/pwa/public/sw.js b/example-projects/pwa/public/sw.js similarity index 100% rename from examples/pwa/public/sw.js rename to example-projects/pwa/public/sw.js diff --git a/examples/pwa/src/main.rs b/example-projects/pwa/src/main.rs similarity index 100% rename from examples/pwa/src/main.rs rename to example-projects/pwa/src/main.rs diff --git a/examples/tailwind/.gitignore b/example-projects/tailwind/.gitignore similarity index 100% rename from examples/tailwind/.gitignore rename to example-projects/tailwind/.gitignore diff --git a/examples/tailwind/Cargo.toml b/example-projects/tailwind/Cargo.toml similarity index 100% rename from examples/tailwind/Cargo.toml rename to example-projects/tailwind/Cargo.toml diff --git a/examples/tailwind/Dioxus.toml b/example-projects/tailwind/Dioxus.toml similarity index 100% rename from examples/tailwind/Dioxus.toml rename to example-projects/tailwind/Dioxus.toml diff --git a/examples/tailwind/README.md b/example-projects/tailwind/README.md similarity index 100% rename from examples/tailwind/README.md rename to example-projects/tailwind/README.md diff --git a/examples/tailwind/input.css b/example-projects/tailwind/input.css similarity index 100% rename from examples/tailwind/input.css rename to example-projects/tailwind/input.css diff --git a/examples/tailwind/public/tailwind.css b/example-projects/tailwind/public/tailwind.css similarity index 100% rename from examples/tailwind/public/tailwind.css rename to example-projects/tailwind/public/tailwind.css diff --git a/examples/tailwind/src/main.rs b/example-projects/tailwind/src/main.rs similarity index 100% rename from examples/tailwind/src/main.rs rename to example-projects/tailwind/src/main.rs diff --git a/examples/tailwind/tailwind.config.js b/example-projects/tailwind/tailwind.config.js similarity index 100% rename from examples/tailwind/tailwind.config.js rename to example-projects/tailwind/tailwind.config.js From ae577bbfc74133d453157f20eb08920c68f6bf35 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 16:58:37 -0700 Subject: [PATCH 117/301] patchy patchy server fns! --- Cargo.lock | 3 + Cargo.toml | 18 +- .../fullstack-hello-world/src/main.rs | 27 ++- packages/cli/src/build/request.rs | 2 +- packages/devtools/src/lib.rs | 4 +- packages/server/Cargo.toml | 2 + packages/server/src/launch.rs | 162 ++++++++++++------ packages/server/src/rt.rs | 122 ++++++------- packages/server/src/serve_config.rs | 2 +- packages/subsecond/subsecond/Cargo.toml | 2 +- packages/subsecond/subsecond/src/lib.rs | 16 +- 11 files changed, 217 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f9f7691b2..30abaaebe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4401,6 +4401,7 @@ dependencies = [ "base64 0.22.1", "bytes", "ciborium", + "dashmap 6.1.0", "dioxus", "dioxus-cli-config", "dioxus-devtools", @@ -4420,6 +4421,7 @@ dependencies = [ "hyper 1.6.0", "hyper-rustls 0.27.5", "hyper-util", + "inventory", "once_cell", "parking_lot", "pin-project", @@ -13362,6 +13364,7 @@ dependencies = [ "serde-wasm-bindgen 0.6.5", "subsecond-macro", "subsecond-types", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 5e9678a86e..b4f7f96e5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,13 +106,13 @@ members = [ "example-projects/file-explorer", # Simple examples that require a crate - "examples/tailwind", - "examples/pwa", - "examples/fullstack-hello-world", - "examples/fullstack-router", - "examples/fullstack-streaming", - "examples/fullstack-desktop", - "examples/fullstack-auth", + "example-projects/tailwind", + "example-projects/pwa", + "example-projects/fullstack-hello-world", + "example-projects/fullstack-router", + "example-projects/fullstack-streaming", + "example-projects/fullstack-desktop", + "example-projects/fullstack-auth", # Playwright tests "packages/playwright-tests/liveview", @@ -288,6 +288,7 @@ bincode = "1.3.3" macro-string = "0.1.3" walkdir = "2.5.0" url = "2.3.1" +inventory = { version = "0.3.5" } # desktop wry = { version = "0.50.1", default-features = false } @@ -363,7 +364,8 @@ incremental = true [profile.wasm-dev] inherits = "dev" -opt-level = 1 +debug = 0 +strip = "debuginfo" [profile.server-dev] inherits = "dev" diff --git a/example-projects/fullstack-hello-world/src/main.rs b/example-projects/fullstack-hello-world/src/main.rs index 1eaeb0563e..6c5f008be9 100644 --- a/example-projects/fullstack-hello-world/src/main.rs +++ b/example-projects/fullstack-hello-world/src/main.rs @@ -4,10 +4,13 @@ //! dx serve --platform web //! ``` -#![allow(non_snake_case, unused)] use dioxus::prelude::*; use serde::{Deserialize, Serialize}; +fn main() { + dioxus::launch(app); +} + fn app() -> Element { let mut count = use_signal(|| 0); let mut text = use_signal(|| "...".to_string()); @@ -25,16 +28,32 @@ fn app() -> Element { text.set(data.clone()); post_server_data(data).await.unwrap(); } + }, "Run a server function!" } + button { + onclick: move |_| async move { + if let Ok(data) = say_hi().await { + text.set(data.clone()); + } else { + text.set("Error".to_string()); + } + }, + "Say hi!" + } "Server said: {text}" } } +#[server] +async fn say_hi() -> Result { + Ok("hello 123".to_string()) +} + #[server] async fn post_server_data(data: String) -> Result<(), ServerFnError> { - println!("Server received: {}", data); + println!("Server recesdasdasdasdasasdaasdsdasdisdasdvedasd: {}", data); Ok(()) } @@ -43,7 +62,3 @@ async fn post_server_data(data: String) -> Result<(), ServerFnError> { async fn get_server_data() -> Result { Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?) } - -fn main() { - dioxus::launch(app); -} diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 7f899fabc5..fda94ba45e 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1247,7 +1247,7 @@ impl BuildRequest { .display() )); cmd.envs(self.env_vars(ctx)?); - tracing::debug!("Using rustc wrapper args: {:#?}", cmd); + tracing::trace!("Using rustc wrapper args: {:#?}", cmd); Ok(cmd) } diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index 0c2c31d585..ef16de2f9c 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -56,8 +56,8 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { }); } -pub fn apply_patch(table: JumpTable) { - unsafe { subsecond::apply_patch(table) }; +pub fn apply_patch(table: JumpTable) -> Result<(), subsecond::PatchError> { + unsafe { subsecond::apply_patch(table) } } /// Connect to the devserver and handle its messages with a callback. diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 3d7315bc15..18b39591ab 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -62,6 +62,8 @@ aws-lc-rs = { version = "1.8.1", optional = true} dioxus-history = { workspace = true } hyper-util = { workspace = true, features = ["full"] } subsecond.workspace = true +inventory = { workspace = true } +dashmap = "6.1.0" [target.'cfg(target_arch = "wasm32")'.dependencies] tokio = { workspace = true, features = ["rt", "sync"] } diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index 5e389dbb67..e5a840d5e4 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -1,6 +1,6 @@ //! A launch function that creates an axum router for the LaunchBuilder -use std::{any::Any, net::SocketAddr}; +use std::{any::Any, collections::HashMap, net::SocketAddr}; use axum::{ body::Body, @@ -9,16 +9,18 @@ use axum::{ routing::IntoMakeService, serve::IncomingStream, }; +use dashmap::DashMap; use dioxus_cli_config::base_path; use dioxus_devtools::DevserverMsg; use dioxus_lib::prelude::*; -use futures_util::{stream::FusedStream, StreamExt}; +use futures_util::{pin_mut, stream::FusedStream, StreamExt}; use hyper::body::Incoming; use hyper_util::server::conn::auto::Builder as HyperBuilder; use hyper_util::{ rt::{TokioExecutor, TokioIo}, service::TowerToHyperService, }; +use server_fn::ServerFnTraitObj; use tokio::net::TcpStream; use tokio_util::task::LocalPoolHandle; use tower::Service; @@ -26,8 +28,8 @@ use tower::ServiceExt as _; // use tower::{Service, ServiceExt}; use crate::{ - render_handler, rt::DioxusRouterExt, RenderHandleState, SSRState, ServeConfig, - ServeConfigBuilder, + register_server_fn_on_router, render_handler, rt::DioxusRouterExt, RenderHandleState, SSRState, + ServeConfig, ServeConfigBuilder, }; type ContextList = Vec Box + Send + Sync>>; @@ -74,28 +76,29 @@ async fn serve_server( .unwrap_or_else(ServeConfig::new); // Extend the config's context providers with the context providers from the launch builder - let platform_config = platform_config.map(|mut cfg| { - let mut contexts = contexts; - let cfg_context_providers = cfg.context_providers.clone(); - for i in 0..cfg_context_providers.len() { - contexts.push(Box::new({ - let cfg_context_providers = cfg_context_providers.clone(); - move || (cfg_context_providers[i])() - })); - } - cfg.context_providers = std::sync::Arc::new(contexts); - cfg - }); + let cfg = platform_config + .map(|mut cfg| { + let mut contexts = contexts; + let cfg_context_providers = cfg.context_providers.clone(); + for i in 0..cfg_context_providers.len() { + contexts.push(Box::new({ + let cfg_context_providers = cfg_context_providers.clone(); + move || (cfg_context_providers[i])() + })); + } + cfg.context_providers = std::sync::Arc::new(contexts); + cfg + }) + .unwrap(); // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address // and we use the generated address the CLI gives us let address = dioxus_cli_config::fullstack_address_or_localhost(); - let config = platform_config.as_ref().ok().cloned(); + let router = axum::Router::new().serve_dioxus_application(cfg.clone(), root); - let router = build_router(root, platform_config); let task_pool = LocalPoolHandle::new(5); - let make_service = router.into_make_service(); + let mut make_service = router.into_make_service(); let listener = tokio::net::TcpListener::bind(address).await.unwrap(); @@ -106,7 +109,8 @@ async fn serve_server( Devtools(DevserverMsg), } - // axum::serve() + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(0); + let mut hr_idx = 0; // Manually loop on accepting connections so we can also respond to devtools messages loop { @@ -130,20 +134,69 @@ async fn serve_server( // We need to somehow get them out... ? // // for now we just support editing existing server functions - Msg::Devtools(devserver_msg) => match devserver_msg { - DevserverMsg::HotReload(hot_reload_msg) => { - if let Some(table) = hot_reload_msg.jump_table { - dioxus_devtools::apply_patch(table); + Msg::Devtools(devserver_msg) => { + match devserver_msg { + DevserverMsg::HotReload(hot_reload_msg) => { + if let Some(table) = hot_reload_msg.jump_table { + use axum::body::Body; + use http::{Method, Request, Response, StatusCode}; + + if let Ok(_) = dioxus_devtools::apply_patch(table) { + let mut new_router = axum::Router::new().serve_static_assets(); + + let server_fn_iter = collect_raw_server_fns(); + + // de-duplicate iteratively by prefering the most recent (first, since it's linked) + let mut server_fn_map: HashMap<_, _> = HashMap::new(); + for f in server_fn_iter.into_iter().rev() { + server_fn_map.insert(f.path(), f); + } + + for (_, f) in server_fn_map { + tracing::info!( + "Registering server function: {:?} {:?}", + f.path(), + f.method() + ); + new_router = crate::register_server_fn_on_router( + f, + new_router, + cfg.context_providers.clone(), + ); + } + + let ssr_state = SSRState::new(&cfg); + + make_service = new_router + .fallback( + axum::routing::get(render_handler).with_state( + RenderHandleState::new(cfg.clone(), root) + .with_ssr_state(ssr_state), + ), + ) + .into_make_service(); + + tracing::info!("Shutting down connections..."); + _ = shutdown_tx.send_modify(|i| { + *i += 1; + hr_idx += 1; + }); + } + } } + DevserverMsg::FullReloadStart => {} + DevserverMsg::FullReloadFailed => {} + DevserverMsg::FullReloadCommand => {} + DevserverMsg::Shutdown => {} } - DevserverMsg::FullReloadStart => {} - DevserverMsg::FullReloadFailed => {} - DevserverMsg::FullReloadCommand => {} - DevserverMsg::Shutdown => {} - }, + } Msg::TcpStream(Err(_)) => {} Msg::TcpStream(Ok((tcp_stream, remote_addr))) => { + tracing::debug!("Accepted connection from {remote_addr}"); + let mut make_service = make_service.clone(); + let mut shutdown_rx = shutdown_rx.clone(); + let mut hr_idx = hr_idx.clone(); task_pool.spawn_pinned(move || async move { let tcp_stream = TokioIo::new(tcp_stream); @@ -179,19 +232,26 @@ async fn serve_server( }); // upgrades needed for websockets - let ret = HyperBuilder::new(TokioExecutor::new()) - .serve_connection_with_upgrades( - tcp_stream, - TowerToHyperService::new(tower_service), - ) - .await; - - if let Err(err) = ret { - // This error only appears when the client doesn't send a request and - // terminate the connection. - // - // If client sends one request then terminate connection whenever, it doesn't - // appear. + let builder = HyperBuilder::new(TokioExecutor::new()); + let connection = builder.serve_connection_with_upgrades( + tcp_stream, + TowerToHyperService::new(tower_service), + ); + + tokio::select! { + res = connection => { + if let Err(err) = res { + // This error only appears when the client doesn't send a request and + // terminate the connection. + // + // If client sends one request then terminate connection whenever, it doesn't + // appear. + } + } + res = shutdown_rx.wait_for(|i| *i == hr_idx + 1) => { + tracing::info!("Shutting down connection server: {res:?}"); + return; + } } }); } @@ -199,6 +259,12 @@ async fn serve_server( } } +pub type AxumServerFn = ServerFnTraitObj, http::Response>; + +pub fn collect_raw_server_fns() -> Vec<&'static AxumServerFn> { + inventory::iter::().into_iter().collect() +} + fn build_router( root: fn() -> Result, platform_config: Result, @@ -206,20 +272,10 @@ fn build_router( let mut base_path = base_path(); let dioxus_router = - axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root); + axum::Router::new().serve_dioxus_application(platform_config.unwrap(), root); let router = dioxus_router; - struct TryIntoResult(Result); - - impl TryInto for TryIntoResult { - type Error = crate::UnableToLoadIndex; - - fn try_into(self) -> Result { - self.0 - } - } - // let mut router; // match base_path.as_deref() { // Some(base_path) => { diff --git a/packages/server/src/rt.rs b/packages/server/src/rt.rs index 1cc7723081..e6808f6081 100644 --- a/packages/server/src/rt.rs +++ b/packages/server/src/rt.rs @@ -1,4 +1,7 @@ -use crate::{render::SSRError, with_server_context, DioxusServerContext, SSRState, ServeConfig}; +use crate::{ + collect_raw_server_fns, render::SSRError, with_server_context, AxumServerFn, + DioxusServerContext, SSRState, ServeConfig, +}; use crate::{ContextProviders, ProvideServerContext}; use axum::body; use axum::extract::State; @@ -10,6 +13,7 @@ use axum::{ }; use dioxus_lib::prelude::{Element, VirtualDom}; use http::header::*; +use server_fn::middleware::BoxedService; use std::sync::Arc; /// A extension trait with utilities for integrating Dioxus with your Axum router. @@ -61,10 +65,8 @@ pub trait DioxusRouterExt: DioxusRouterFnExt { /// rsx! { "Hello World" } /// } /// ``` - fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self + fn serve_dioxus_application(self, cfg: ServeConfig, app: fn() -> Element) -> Self where - Cfg: TryInto, - Error: std::error::Error, Self: Sized; } @@ -116,35 +118,18 @@ where self } - fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self - where - Cfg: TryInto, - Error: std::error::Error, - { - let cfg = cfg.try_into(); - let context_providers = cfg - .as_ref() - .map(|cfg| cfg.context_providers.clone()) - .unwrap_or_default(); - + fn serve_dioxus_application(self, cfg: ServeConfig, app: fn() -> Element) -> Self { // Add server functions and render index.html let server = self .serve_static_assets() - .register_server_functions_with_context(context_providers); - - match cfg { - Ok(cfg) => { - let ssr_state = SSRState::new(&cfg); - server.fallback( - get(render_handler) - .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)), - ) - } - Err(err) => { - tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err); - server - } - } + .register_server_functions_with_context(cfg.context_providers.clone()); + + let ssr_state = SSRState::new(&cfg); + + server.fallback( + get(render_handler) + .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)), + ) } } @@ -204,61 +189,56 @@ where mut self, context_providers: ContextProviders, ) -> Self { - use http::method::Method; - tracing::info!("Registering server functions..."); - for (path, method) in server_fn::axum::server_fn_paths() { - tracing::info!("Registering server function: {} {}", method, path); - let context_providers = context_providers.clone(); - let handler = move |req| handle_server_fns_inner(path, context_providers, req); - self = match method { - Method::GET => self.route(path, get(handler)), - Method::POST => self.route(path, post(handler)), - Method::PUT => self.route(path, put(handler)), - _ => unimplemented!("Unsupported server function method: {}", method), - }; + for f in collect_raw_server_fns() { + self = register_server_fn_on_router(f, self, context_providers.clone()); } + // for (path, method) in server_fn::axum::server_fn_paths() { + // if let Some(service) = server_fn::axum::get_server_fn_service(&path, method.clone()) { + // } + // } self } } +pub fn register_server_fn_on_router( + f: &'static AxumServerFn, + router: Router, + context_providers: ContextProviders, +) -> Router +where + S: Send + Sync + Clone + 'static, +{ + use http::method::Method; + let path = f.path(); + let method = f.method(); + + tracing::info!("Registering server function: {} {}", method, path); + let handler = move |req| handle_server_fns_inner(f, context_providers, req); + match method { + Method::GET => router.route(path, get(handler)), + Method::POST => router.route(path, post(handler)), + Method::PUT => router.route(path, put(handler)), + _ => unimplemented!("Unsupported server function method: {}", method), + } +} + /// A handler for Dioxus server functions. This will run the server function and return the result. async fn handle_server_fns_inner( - path: &str, + f: &AxumServerFn, additional_context: ContextProviders, req: Request, ) -> Response { use server_fn::middleware::Service; - let path_string = path.to_string(); - let (parts, body) = req.into_parts(); let req = Request::from_parts(parts.clone(), body); - let method = req.method().clone(); - - let Some(mut service) = server_fn::axum::get_server_fn_service(&path_string, method) else { - return Response::builder().status(StatusCode::BAD_REQUEST).body( - { - #[cfg(target_family = "wasm")] - { - Body::from(format!( - "No server function found for path: {path_string}\nYou may need to explicitly register the server function with `register_explicit`, rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.", - )) - } - #[cfg(not(target_family = "wasm"))] - { - Body::from(format!( - "No server function found for path: {path_string}\nYou may need to rebuild your wasm binary to update a server function link or make sure the prefix your server and client use for server functions match.", - )) - } - } - ).unwrap(); - }; // Create the server context with info from the request let server_context = DioxusServerContext::new(parts); + // Provide additional context from the render state add_server_context(&server_context, &additional_context); @@ -271,6 +251,18 @@ async fn handle_server_fns_inner( .unwrap_or(false); let referrer = req.headers().get(REFERER).cloned(); + // this is taken from server_fn source... + // + // server_fn::axum::get_server_fn_service + let mut service = { + let middleware = f.middleware(); + let mut service = BoxedService::new(f.clone()); + for middleware in middleware { + service = middleware.layer(service); + } + service + }; + // actually run the server fn (which may use the server context) let fut = with_server_context(server_context.clone(), || service.run(req)); diff --git a/packages/server/src/serve_config.rs b/packages/server/src/serve_config.rs index 65b0c79359..b66bb5d8d0 100644 --- a/packages/server/src/serve_config.rs +++ b/packages/server/src/serve_config.rs @@ -396,7 +396,7 @@ pub(crate) fn public_path() -> PathBuf { } /// An error that can occur when loading the index.html file -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct UnableToLoadIndex(PathBuf); impl std::fmt::Display for UnableToLoadIndex { diff --git a/packages/subsecond/subsecond/Cargo.toml b/packages/subsecond/subsecond/Cargo.toml index 8e8ff04248..e4919f0f66 100644 --- a/packages/subsecond/subsecond/Cargo.toml +++ b/packages/subsecond/subsecond/Cargo.toml @@ -8,7 +8,7 @@ serde = { version = "1.0.203", features = ["derive"] } subsecond-macro = { workspace = true } subsecond-types = { workspace = true } - +thiserror = { workspace = true } wasm-bindgen = { workspace = true } wasm-bindgen-futures = { workspace = true } js-sys = { workspace = true} diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index c4c96c05da..418f871567 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -365,7 +365,7 @@ pub fn register_handler(handler: Arc) { /// /// This function will load the library and thus allocates. In cannot be used when the program is /// stopped (ie in a signal handler). -pub unsafe fn apply_patch(mut table: JumpTable) { +pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> { // As a form of integrity checking, we use the ASLR reference from the incoming jump table to assert that it is intended to patch us // #[cfg(any(unix, windows))] // if table.aslr_reference != aslr_reference() as u64 { @@ -387,13 +387,10 @@ pub unsafe fn apply_patch(mut table: JumpTable) { #[cfg(not(target_os = "android"))] let lib = Box::leak(Box::new({ match libloading::Library::new(&table.lib) { - Ok(lib) => { - println!("Loaded library success!: {:?}", &table.lib); - lib - } + Ok(lib) => lib, err => { eprintln!("Failed to load library: {:?}", err); - return; + return Err(PatchError::CantLoadPatch); } } })); @@ -547,6 +544,13 @@ pub unsafe fn apply_patch(mut table: JumpTable) { unsafe { commit_patch(table) }; }); + + Ok(()) +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PatchError { + CantLoadPatch, } unsafe fn commit_patch(table: JumpTable) { From 73dc824723616b1a3f4c7fe0de2356ed216969a6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 17:15:37 -0700 Subject: [PATCH 118/301] rollback some random changes --- .../fullstack-hello-world/src/main.rs | 39 ++----------------- packages/manganis/manganis-core/src/entry.rs | 26 ------------- packages/manganis/manganis-core/src/fnmeta.rs | 0 packages/manganis/manganis-core/src/lib.rs | 6 --- .../manganis/manganis-core/src/options.rs | 4 -- packages/server-macro/src/lib.rs | 2 +- 6 files changed, 4 insertions(+), 73 deletions(-) delete mode 100644 packages/manganis/manganis-core/src/entry.rs delete mode 100644 packages/manganis/manganis-core/src/fnmeta.rs diff --git a/example-projects/fullstack-hello-world/src/main.rs b/example-projects/fullstack-hello-world/src/main.rs index 6c5f008be9..5e4239b805 100644 --- a/example-projects/fullstack-hello-world/src/main.rs +++ b/example-projects/fullstack-hello-world/src/main.rs @@ -5,40 +5,19 @@ //! ``` use dioxus::prelude::*; -use serde::{Deserialize, Serialize}; fn main() { dioxus::launch(app); } fn app() -> Element { - let mut count = use_signal(|| 0); let mut text = use_signal(|| "...".to_string()); - let server_future = use_server_future(get_server_data)?; rsx! { - document::Link { href: asset!("/assets/hello.css"), rel: "stylesheet" } - h1 { "High-Five counter: {count}" } - button { onclick: move |_| count += 1, "Up high!" } - button { onclick: move |_| count -= 1, "Down low!" } + h1 { "Hot patch serverfns!" } button { onclick: move |_| async move { - if let Ok(data) = get_server_data().await { - println!("Client received: {}", data); - text.set(data.clone()); - post_server_data(data).await.unwrap(); - } - - }, - "Run a server function!" - } - button { - onclick: move |_| async move { - if let Ok(data) = say_hi().await { - text.set(data.clone()); - } else { - text.set("Error".to_string()); - } + text.set(say_hi().await.unwrap()); }, "Say hi!" } @@ -48,17 +27,5 @@ fn app() -> Element { #[server] async fn say_hi() -> Result { - Ok("hello 123".to_string()) -} - -#[server] -async fn post_server_data(data: String) -> Result<(), ServerFnError> { - println!("Server recesdasdasdasdasasdaasdsdasdisdasdvedasd: {}", data); - - Ok(()) -} - -#[server] -async fn get_server_data() -> Result { - Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?) + Ok("Hello from the server!".to_string()) } diff --git a/packages/manganis/manganis-core/src/entry.rs b/packages/manganis/manganis-core/src/entry.rs deleted file mode 100644 index fb33927402..0000000000 --- a/packages/manganis/manganis-core/src/entry.rs +++ /dev/null @@ -1,26 +0,0 @@ -use const_serialize::SerializeConst; - -use crate::BundledAsset; - -#[derive( - Debug, - PartialEq, - PartialOrd, - Clone, - Copy, - Hash, - SerializeConst, - serde::Serialize, - serde::Deserialize, -)] -#[repr(C, u8)] -#[non_exhaustive] -pub enum ManganisEntry { - /// An asset (ie a file) that should be copied by the bundler with some options. - Asset(BundledAsset), - - /// A function that exports some additional metadata - /// - /// The function will end up in its own section - Metadata(), -} diff --git a/packages/manganis/manganis-core/src/fnmeta.rs b/packages/manganis/manganis-core/src/fnmeta.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/manganis/manganis-core/src/lib.rs b/packages/manganis/manganis-core/src/lib.rs index f7daced1f3..52e6ac80b2 100644 --- a/packages/manganis/manganis-core/src/lib.rs +++ b/packages/manganis/manganis-core/src/lib.rs @@ -19,9 +19,3 @@ pub use asset::*; pub mod linker; pub mod hash; - -mod fnmeta; -pub use fnmeta::*; - -pub mod entry; -pub use entry::*; diff --git a/packages/manganis/manganis-core/src/options.rs b/packages/manganis/manganis-core/src/options.rs index 8136be96f9..714c1f9007 100644 --- a/packages/manganis/manganis-core/src/options.rs +++ b/packages/manganis/manganis-core/src/options.rs @@ -19,16 +19,12 @@ use crate::{CssAssetOptions, FolderAssetOptions, ImageAssetOptions, JsAssetOptio pub enum AssetOptions { /// An image asset Image(ImageAssetOptions), - /// A folder asset Folder(FolderAssetOptions), - /// A css asset Css(CssAssetOptions), - /// A javascript asset Js(JsAssetOptions), - /// An unknown asset Unknown, } diff --git a/packages/server-macro/src/lib.rs b/packages/server-macro/src/lib.rs index dc32b9ba01..66e9b30335 100644 --- a/packages/server-macro/src/lib.rs +++ b/packages/server-macro/src/lib.rs @@ -148,7 +148,7 @@ use syn::{ #[proc_macro_attribute] pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { // If there is no input codec, use json as the default - // let args = default_json_codec(args); + let args = default_json_codec(args); match server_macro_impl( args.into(), From a50a8d8d04d7dd805b2c7df00e00c96af950b301 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 17:27:37 -0700 Subject: [PATCH 119/301] unwind the js files --- .vscode/settings.json | 3 + packages/interpreter/src/js/common.js | 94 +-------- packages/interpreter/src/js/core.js | 274 +------------------------- 3 files changed, 5 insertions(+), 366 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1078f85852..df64c95194 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,9 @@ "[handlebars]": { "editor.formatOnSave": false }, + "[javascript]": { + "editor.formatOnSave": false + }, // "rust-analyzer.check.workspace": true, // "rust-analyzer.check.workspace": false, // "rust-analyzer.check.features": "all", diff --git a/packages/interpreter/src/js/common.js b/packages/interpreter/src/js/common.js index 288799b5d6..d66dce32bd 100644 --- a/packages/interpreter/src/js/common.js +++ b/packages/interpreter/src/js/common.js @@ -1,93 +1 @@ -function setAttributeInner(node, field, value, ns) { - if (ns === "style") { - node.style.setProperty(field, value); - return; - } - if (ns) { - node.setAttributeNS(ns, field, value); - return; - } - switch (field) { - case "value": - if (node.value !== value) node.value = value; - break; - case "initial_value": - node.defaultValue = value; - break; - case "checked": - node.checked = truthy(value); - break; - case "initial_checked": - node.defaultChecked = truthy(value); - break; - case "selected": - node.selected = truthy(value); - break; - case "initial_selected": - node.defaultSelected = truthy(value); - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - case "multiple": - setAttributeDefault(node, field, value); - let options = node.options; - for (let option of options) option.selected = option.defaultSelected; - break; - default: - setAttributeDefault(node, field, value); - } -} -var setAttributeDefault = function (node, field, value) { - if (!truthy(value) && isBoolAttr(field)) node.removeAttribute(field); - else node.setAttribute(field, value); - }, - truthy = function (val) { - return val === "true" || val === !0; - }, - isBoolAttr = function (field) { - switch (field) { - case "allowfullscreen": - case "allowpaymentrequest": - case "async": - case "autofocus": - case "autoplay": - case "checked": - case "controls": - case "default": - case "defer": - case "disabled": - case "formnovalidate": - case "hidden": - case "ismap": - case "itemscope": - case "loop": - case "multiple": - case "muted": - case "nomodule": - case "novalidate": - case "open": - case "playsinline": - case "readonly": - case "required": - case "reversed": - case "selected": - case "truespeed": - case "webkitdirectory": - return !0; - default: - return !1; - } - }; -function retrieveFormValues(form) { - const formData = new FormData(form), - contents = {}; - return ( - formData.forEach((value, key) => { - if (contents[key]) contents[key].push(value); - else contents[key] = [value]; - }), - { valid: form.checkValidity(), values: contents } - ); -} -export { setAttributeInner, retrieveFormValues }; +function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;case"multiple":setAttributeDefault(node,field,value);let options=node.options;for(let option of options)option.selected=option.defaultSelected;break;default:setAttributeDefault(node,field,value)}}var setAttributeDefault=function(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)},truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};function retrieveFormValues(form){const formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}export{setAttributeInner,retrieveFormValues}; diff --git a/packages/interpreter/src/js/core.js b/packages/interpreter/src/js/core.js index 89e4b0173a..db80e86ec2 100644 --- a/packages/interpreter/src/js/core.js +++ b/packages/interpreter/src/js/core.js @@ -1,273 +1 @@ -function setAttributeInner(node, field, value, ns) { - if (ns === "style") { - node.style.setProperty(field, value); - return; - } - if (ns) { - node.setAttributeNS(ns, field, value); - return; - } - switch (field) { - case "value": - if (node.value !== value) node.value = value; - break; - case "initial_value": - node.defaultValue = value; - break; - case "checked": - node.checked = truthy(value); - break; - case "initial_checked": - node.defaultChecked = truthy(value); - break; - case "selected": - node.selected = truthy(value); - break; - case "initial_selected": - node.defaultSelected = truthy(value); - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - case "multiple": - setAttributeDefault(node, field, value); - let options = node.options; - for (let option of options) option.selected = option.defaultSelected; - break; - default: - setAttributeDefault(node, field, value); - } -} -var setAttributeDefault = function (node, field, value) { - if (!truthy(value) && isBoolAttr(field)) node.removeAttribute(field); - else node.setAttribute(field, value); - }, - truthy = function (val) { - return val === "true" || val === !0; - }, - isBoolAttr = function (field) { - switch (field) { - case "allowfullscreen": - case "allowpaymentrequest": - case "async": - case "autofocus": - case "autoplay": - case "checked": - case "controls": - case "default": - case "defer": - case "disabled": - case "formnovalidate": - case "hidden": - case "ismap": - case "itemscope": - case "loop": - case "multiple": - case "muted": - case "nomodule": - case "novalidate": - case "open": - case "playsinline": - case "readonly": - case "required": - case "reversed": - case "selected": - case "truespeed": - case "webkitdirectory": - return !0; - default: - return !1; - } - }; -class BaseInterpreter { - global; - local; - root; - handler; - resizeObserver; - intersectionObserver; - nodes; - stack; - templates; - m; - constructor() {} - initialize(root, handler = null) { - (this.global = {}), - (this.local = {}), - (this.root = root), - (this.nodes = [root]), - (this.stack = [root]), - (this.templates = {}), - (this.handler = handler), - root.setAttribute("data-dioxus-id", "0"); - } - handleResizeEvent(entry) { - const target = entry.target; - let event = new CustomEvent("resize", { bubbles: !1, detail: entry }); - target.dispatchEvent(event); - } - createResizeObserver(element) { - if (!this.resizeObserver) - this.resizeObserver = new ResizeObserver((entries) => { - for (let entry of entries) this.handleResizeEvent(entry); - }); - this.resizeObserver.observe(element); - } - removeResizeObserver(element) { - if (this.resizeObserver) this.resizeObserver.unobserve(element); - } - handleIntersectionEvent(entry) { - const target = entry.target; - let event = new CustomEvent("visible", { bubbles: !1, detail: entry }); - target.dispatchEvent(event); - } - createIntersectionObserver(element) { - if (!this.intersectionObserver) - this.intersectionObserver = new IntersectionObserver((entries) => { - for (let entry of entries) this.handleIntersectionEvent(entry); - }); - this.intersectionObserver.observe(element); - } - removeIntersectionObserver(element) { - if (this.intersectionObserver) this.intersectionObserver.unobserve(element); - } - createListener(event_name, element, bubbles) { - if (event_name == "resize") this.createResizeObserver(element); - else if (event_name == "visible") this.createIntersectionObserver(element); - if (bubbles) - if (this.global[event_name] === void 0) - (this.global[event_name] = { active: 1, callback: this.handler }), - this.root.addEventListener(event_name, this.handler); - else this.global[event_name].active++; - else { - const id = element.getAttribute("data-dioxus-id"); - if (!this.local[id]) this.local[id] = {}; - element.addEventListener(event_name, this.handler); - } - } - removeListener(element, event_name, bubbles) { - if (event_name == "resize") this.removeResizeObserver(element); - else if (event_name == "visible") this.removeIntersectionObserver(element); - else if (bubbles) this.removeBubblingListener(event_name); - else this.removeNonBubblingListener(element, event_name); - } - removeBubblingListener(event_name) { - if ( - (this.global[event_name].active--, this.global[event_name].active === 0) - ) - this.root.removeEventListener( - event_name, - this.global[event_name].callback - ), - delete this.global[event_name]; - } - removeNonBubblingListener(element, event_name) { - const id = element.getAttribute("data-dioxus-id"); - if ( - (delete this.local[id][event_name], - Object.keys(this.local[id]).length === 0) - ) - delete this.local[id]; - element.removeEventListener(event_name, this.handler); - } - removeAllNonBubblingListeners(element) { - const id = element.getAttribute("data-dioxus-id"); - delete this.local[id]; - } - getNode(id) { - return this.nodes[id]; - } - pushRoot(node) { - this.stack.push(node); - } - appendChildren(id, many) { - const root = this.nodes[id], - els = this.stack.splice(this.stack.length - many); - for (let k = 0; k < many; k++) root.appendChild(els[k]); - } - loadChild(ptr, len) { - let node = this.stack[this.stack.length - 1], - ptr_end = ptr + len; - for (; ptr < ptr_end; ptr++) { - let end = this.m.getUint8(ptr); - for (node = node.firstChild; end > 0; end--) node = node.nextSibling; - } - return node; - } - saveTemplate(nodes, tmpl_id) { - this.templates[tmpl_id] = nodes; - } - hydrate_node(hydrateNode, ids) { - const split = hydrateNode.getAttribute("data-node-hydration").split(","), - id = ids[parseInt(split[0])]; - if (((this.nodes[id] = hydrateNode), split.length > 1)) { - (hydrateNode.listening = split.length - 1), - hydrateNode.setAttribute("data-dioxus-id", id.toString()); - for (let j = 1; j < split.length; j++) { - const split2 = split[j].split(":"), - event_name = split2[0], - bubbles = split2[1] === "1"; - this.createListener(event_name, hydrateNode, bubbles); - } - } - } - hydrate(ids, underNodes) { - for (let i = 0; i < underNodes.length; i++) { - const under = underNodes[i]; - if (under instanceof HTMLElement) { - if (under.getAttribute("data-node-hydration")) - this.hydrate_node(under, ids); - const hydrateNodes = under.querySelectorAll("[data-node-hydration]"); - for (let i2 = 0; i2 < hydrateNodes.length; i2++) - this.hydrate_node(hydrateNodes[i2], ids); - } - const treeWalker = document.createTreeWalker( - under, - NodeFilter.SHOW_COMMENT - ); - let nextSibling = under.nextSibling, - continueToNextNode = () => { - if (!treeWalker.nextNode()) return !1; - return treeWalker.currentNode !== nextSibling; - }; - while (treeWalker.currentNode) { - const currentNode = treeWalker.currentNode; - if (currentNode.nodeType === Node.COMMENT_NODE) { - const id = currentNode.textContent, - placeholderSplit = id.split("placeholder"); - if (placeholderSplit.length > 1) { - if ( - ((this.nodes[ids[parseInt(placeholderSplit[1])]] = currentNode), - !continueToNextNode()) - ) - break; - continue; - } - const textNodeSplit = id.split("node-id"); - if (textNodeSplit.length > 1) { - let next = currentNode.nextSibling; - currentNode.remove(); - let commentAfterText, textNode; - if (next.nodeType === Node.COMMENT_NODE) { - const newText = next.parentElement.insertBefore( - document.createTextNode(""), - next - ); - (commentAfterText = next), (textNode = newText); - } else (textNode = next), (commentAfterText = textNode.nextSibling); - (treeWalker.currentNode = commentAfterText), - (this.nodes[ids[parseInt(textNodeSplit[1])]] = textNode); - let exit = currentNode === under || !continueToNextNode(); - if ((commentAfterText.remove(), exit)) break; - continue; - } - } - if (!continueToNextNode()) break; - } - } - } - setAttributeInner(node, field, value, ns) { - setAttributeInner(node, field, value, ns); - } -} -export { BaseInterpreter }; +function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.value!==value)node.value=value;break;case"initial_value":node.defaultValue=value;break;case"checked":node.checked=truthy(value);break;case"initial_checked":node.defaultChecked=truthy(value);break;case"selected":node.selected=truthy(value);break;case"initial_selected":node.defaultSelected=truthy(value);break;case"dangerous_inner_html":node.innerHTML=value;break;case"multiple":setAttributeDefault(node,field,value);let options=node.options;for(let option of options)option.selected=option.defaultSelected;break;default:setAttributeDefault(node,field,value)}}var setAttributeDefault=function(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)},truthy=function(val){return val==="true"||val===!0},isBoolAttr=function(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}};class BaseInterpreter{global;local;root;handler;resizeObserver;intersectionObserver;nodes;stack;templates;m;constructor(){}initialize(root,handler=null){this.global={},this.local={},this.root=root,this.nodes=[root],this.stack=[root],this.templates={},this.handler=handler,root.setAttribute("data-dioxus-id","0")}handleResizeEvent(entry){const target=entry.target;let event=new CustomEvent("resize",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createResizeObserver(element){if(!this.resizeObserver)this.resizeObserver=new ResizeObserver((entries)=>{for(let entry of entries)this.handleResizeEvent(entry)});this.resizeObserver.observe(element)}removeResizeObserver(element){if(this.resizeObserver)this.resizeObserver.unobserve(element)}handleIntersectionEvent(entry){const target=entry.target;let event=new CustomEvent("visible",{bubbles:!1,detail:entry});target.dispatchEvent(event)}createIntersectionObserver(element){if(!this.intersectionObserver)this.intersectionObserver=new IntersectionObserver((entries)=>{for(let entry of entries)this.handleIntersectionEvent(entry)});this.intersectionObserver.observe(element)}removeIntersectionObserver(element){if(this.intersectionObserver)this.intersectionObserver.unobserve(element)}createListener(event_name,element,bubbles){if(event_name=="resize")this.createResizeObserver(element);else if(event_name=="visible")this.createIntersectionObserver(element);if(bubbles)if(this.global[event_name]===void 0)this.global[event_name]={active:1,callback:this.handler},this.root.addEventListener(event_name,this.handler);else this.global[event_name].active++;else{const id=element.getAttribute("data-dioxus-id");if(!this.local[id])this.local[id]={};element.addEventListener(event_name,this.handler)}}removeListener(element,event_name,bubbles){if(event_name=="resize")this.removeResizeObserver(element);else if(event_name=="visible")this.removeIntersectionObserver(element);else if(bubbles)this.removeBubblingListener(event_name);else this.removeNonBubblingListener(element,event_name)}removeBubblingListener(event_name){if(this.global[event_name].active--,this.global[event_name].active===0)this.root.removeEventListener(event_name,this.global[event_name].callback),delete this.global[event_name]}removeNonBubblingListener(element,event_name){const id=element.getAttribute("data-dioxus-id");if(delete this.local[id][event_name],Object.keys(this.local[id]).length===0)delete this.local[id];element.removeEventListener(event_name,this.handler)}removeAllNonBubblingListeners(element){const id=element.getAttribute("data-dioxus-id");delete this.local[id]}getNode(id){return this.nodes[id]}pushRoot(node){this.stack.push(node)}appendChildren(id,many){const root=this.nodes[id],els=this.stack.splice(this.stack.length-many);for(let k=0;k0;end--)node=node.nextSibling}return node}saveTemplate(nodes,tmpl_id){this.templates[tmpl_id]=nodes}hydrate_node(hydrateNode,ids){const split=hydrateNode.getAttribute("data-node-hydration").split(","),id=ids[parseInt(split[0])];if(this.nodes[id]=hydrateNode,split.length>1){hydrateNode.listening=split.length-1,hydrateNode.setAttribute("data-dioxus-id",id.toString());for(let j=1;j{if(!treeWalker.nextNode())return!1;return treeWalker.currentNode!==nextSibling};while(treeWalker.currentNode){const currentNode=treeWalker.currentNode;if(currentNode.nodeType===Node.COMMENT_NODE){const id=currentNode.textContent,placeholderSplit=id.split("placeholder");if(placeholderSplit.length>1){if(this.nodes[ids[parseInt(placeholderSplit[1])]]=currentNode,!continueToNextNode())break;continue}const textNodeSplit=id.split("node-id");if(textNodeSplit.length>1){let next=currentNode.nextSibling;currentNode.remove();let commentAfterText,textNode;if(next.nodeType===Node.COMMENT_NODE){const newText=next.parentElement.insertBefore(document.createTextNode(""),next);commentAfterText=next,textNode=newText}else textNode=next,commentAfterText=textNode.nextSibling;treeWalker.currentNode=commentAfterText,this.nodes[ids[parseInt(textNodeSplit[1])]]=textNode;let exit=currentNode===under||!continueToNextNode();if(commentAfterText.remove(),exit)break;continue}}if(!continueToNextNode())break}}}setAttributeInner(node,field,value,ns){setAttributeInner(node,field,value,ns)}}export{BaseInterpreter}; From bc6a17d928f2ee7163398c920702c43b1ce72217 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 17:31:49 -0700 Subject: [PATCH 120/301] rollback hash --- packages/document/src/js/hash.txt | 2 +- packages/document/src/ts/eval.ts | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/document/src/js/hash.txt b/packages/document/src/js/hash.txt index 72deb92d11..faaeb89f2e 100644 --- a/packages/document/src/js/hash.txt +++ b/packages/document/src/js/hash.txt @@ -1 +1 @@ -[3101459268373648704, 8375185156499858125] \ No newline at end of file +[206827801705263822, 8375185156499858125] \ No newline at end of file diff --git a/packages/document/src/ts/eval.ts b/packages/document/src/ts/eval.ts index df3e972414..4440fe9dfd 100644 --- a/packages/document/src/ts/eval.ts +++ b/packages/document/src/ts/eval.ts @@ -1,6 +1,6 @@ // Handle communication between rust and evaluating javascript -class Channel { +export class Channel { pending: any[]; waiting: ((data: any) => void)[]; @@ -32,7 +32,7 @@ class Channel { } } -class WeakDioxusChannel { +export class WeakDioxusChannel { inner: WeakRef; constructor(channel: DioxusChannel) { @@ -56,7 +56,7 @@ class WeakDioxusChannel { } } -abstract class DioxusChannel { +export abstract class DioxusChannel { // Return a weak reference to this channel weak(): WeakDioxusChannel { return new WeakDioxusChannel(this); @@ -68,5 +68,3 @@ abstract class DioxusChannel { // Receive data sent from javascript in rust abstract rustRecv(): Promise; } - -export { DioxusChannel, Channel, WeakDioxusChannel }; From 764384507e5733449bd0ad99ccfd2df5f6d578ec Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 17:32:38 -0700 Subject: [PATCH 121/301] no need for patch --- packages/dioxus/src/hotpatch.rs | 7 ------- packages/dioxus/src/lib.rs | 2 -- 2 files changed, 9 deletions(-) delete mode 100644 packages/dioxus/src/hotpatch.rs diff --git a/packages/dioxus/src/hotpatch.rs b/packages/dioxus/src/hotpatch.rs deleted file mode 100644 index 74157d4384..0000000000 --- a/packages/dioxus/src/hotpatch.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::{any::TypeId, rc::Rc}; - -use dioxus_core::{ - prelude::{provide_context, provide_root_context}, - use_hook, Element, -}; -// use dioxus_devtools::Devtools; diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index 1b6ac2d4fa..ff47951695 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -32,8 +32,6 @@ pub use dioxus_core::{CapturedError, Ok, Result}; #[cfg_attr(docsrs, doc(cfg(feature = "launch")))] mod launch; -mod hotpatch; - #[cfg(feature = "launch")] #[cfg_attr(docsrs, doc(cfg(feature = "launch")))] pub use crate::launch::*; From d27fb4f07d2005ef09461fbeed4dd7dba4ff696f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 17:34:43 -0700 Subject: [PATCH 122/301] more cleanups --- packages/cli-opt/Cargo.toml | 2 +- packages/cli-opt/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index 1d650b56fd..63414c54b5 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react"] anyhow = { workspace = true } manganis = { workspace = true } manganis-core = { workspace = true } -object = {workspace = true, features = ["wasm"] } +object = { workspace = true, features = ["wasm"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } const-serialize = { workspace = true, features = ["serde"] } diff --git a/packages/cli-opt/src/lib.rs b/packages/cli-opt/src/lib.rs index 5823add405..97c934a747 100644 --- a/packages/cli-opt/src/lib.rs +++ b/packages/cli-opt/src/lib.rs @@ -102,8 +102,7 @@ impl AssetManifest { continue; }; - // Check if the link section matches the asset section for one of the platforms we support. - // This may not be the current platform if the user is cross compiling + // Check if the link section matches the asset section for one of the platforms we support. This may not be the current platform if the user is cross compiling let matches = LinkSection::ALL .iter() .any(|x| x.link_section == section_name); From e74e520873eba59d514051804a5ba9d4c501c4b8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 17:44:40 -0700 Subject: [PATCH 123/301] lil bit more cleanup, remove some old cruft --- packages/devtools-types/src/lib.rs | 7 +- .../subsecond-cli-support/src/lib.rs | 673 ++++++++++++++++- .../subsecond-cli-support/src/partial.rs | 679 ------------------ .../subsecond/subsecond-cli/src/index.html | 69 -- packages/subsecond/subsecond/src/lib.rs | 11 - 5 files changed, 672 insertions(+), 767 deletions(-) delete mode 100644 packages/subsecond/subsecond-cli-support/src/partial.rs diff --git a/packages/devtools-types/src/lib.rs b/packages/devtools-types/src/lib.rs index 9271eae13c..e2c091a50d 100644 --- a/packages/devtools-types/src/lib.rs +++ b/packages/devtools-types/src/lib.rs @@ -1,12 +1,10 @@ use dioxus_core::internal::HotReloadTemplateWithLocation; use serde::{Deserialize, Serialize}; -use std::{ - collections::{HashMap, HashSet}, - path::PathBuf, -}; +use std::path::PathBuf; use subsecond_types::JumpTable; /// A message the hot reloading server sends to the client +#[non_exhaustive] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub enum DevserverMsg { /// Attempt a hotreload @@ -29,6 +27,7 @@ pub enum DevserverMsg { /// A message the client sends from the frontend to the devserver /// /// This is used to communicate with the devserver +#[non_exhaustive] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub enum ClientMsg { Initialize { diff --git a/packages/subsecond/subsecond-cli-support/src/lib.rs b/packages/subsecond/subsecond-cli-support/src/lib.rs index 11f35802d9..36c8ce55bf 100644 --- a/packages/subsecond/subsecond-cli-support/src/lib.rs +++ b/packages/subsecond/subsecond-cli-support/src/lib.rs @@ -3,8 +3,11 @@ use itertools::Itertools; use memmap::{Mmap, MmapOptions}; use object::{ macho::{self, ARM64_RELOC_UNSIGNED, MH_TWOLEVEL}, - read::File, - write::{MachOBuildVersion, Relocation, SectionId, StandardSection, Symbol, SymbolSection}, + read::{File, Relocation as ReadRelocation}, + write::{ + MachOBuildVersion, Relocation as WriteRelocation, SectionId, StandardSection, Symbol, + SymbolSection, + }, Architecture, BinaryFormat, Endianness, Object, ObjectSection, ObjectSymbol, ObjectSymbolTable, RelocationFlags, RelocationTarget, SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, @@ -32,8 +35,6 @@ use walrus::{ ModuleConfig, RawCustomSection, ValType, }; -pub mod partial; - pub fn create_jump_table( original: &Path, patch: &Path, @@ -709,3 +710,667 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result> { Ok(symbols) } + +async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, out_path: PathBuf) { + let mut object = ObjectDiff::new().unwrap(); + object.load().unwrap(); + let diff = object.diff().unwrap(); + + // Assemble the stub + let stub_data = make_stub_file(proc_main_addr, patch_target, diff.adrp_imports); + let stub_file = workspace_dir().join("stub.o"); + std::fs::write(&stub_file, stub_data).unwrap(); +} + +struct ObjectDiffResult<'a> { + adrp_imports: HashSet<&'a str>, + modified_files: Vec<(&'a PathBuf, &'a HashSet)>, + modified_symbols: HashSet<&'a String>, +} + +struct ObjectDiff { + old: BTreeMap, + new: BTreeMap, + modified_files: HashMap>, + modified_symbols: HashSet, + parents: HashMap>, +} + +impl ObjectDiff { + fn new() -> Result { + Ok(Self { + old: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-old"))?, + new: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-new"))?, + modified_files: Default::default(), + modified_symbols: Default::default(), + parents: Default::default(), + }) + } + + fn diff(&self) -> Result> { + let all_exports = self + .new + .iter() + .flat_map(|(_, f)| f.file.exports().unwrap()) + .map(|e| e.name().to_utf8()) + .collect::>(); + + let mut adrp_imports = HashSet::new(); + + let mut satisfied_exports = HashSet::new(); + + let modified_symbols = self.modified_symbols.iter().collect::>(); + + if modified_symbols.is_empty() { + println!("No modified symbols"); + } + + let mut modified_log = String::new(); + for m in modified_symbols.iter() { + let path = self.find_path_to_main(m); + modified_log.push_str(&format!("{m}\n")); + modified_log.push_str(&format!("{path:#?}\n")); + } + std::fs::write(workspace_dir().join("modified_symbols.txt"), modified_log).unwrap(); + + let modified = self + .modified_files + .iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .collect::>(); + + // Figure out which symbols are required from *existing* code + // We're going to create a stub `.o` file that satisfies these by jumping into the original code via a dynamic lookup / and or literally just manually doing it + for fil in modified.iter() { + let f = self + .new + .get(fil.0.file_name().unwrap().to_str().unwrap()) + .unwrap(); + + for i in f.file.imports().unwrap() { + if all_exports.contains(i.name().to_utf8()) { + adrp_imports.insert(i.name().to_utf8()); + } + } + + for e in f.file.exports().unwrap() { + satisfied_exports.insert(e.name().to_utf8()); + } + } + + // Remove any imports that are indeed satisifed + for s in satisfied_exports.iter() { + adrp_imports.remove(s); + } + + Ok(ObjectDiffResult { + adrp_imports, + modified_files: modified, + modified_symbols, + }) + } + + fn load(&mut self) -> Result<()> { + let num_right = self.new.len(); + + let keys = self.new.keys().cloned().collect::>(); + for (idx, f) in keys.iter().enumerate() { + println!("----- {:?} {}/{} -----", f, idx, num_right); + + let changed_before = self.modified_symbols.len(); + self.load_file(f)?; + let changed_after = self.modified_symbols.len(); + + if changed_after > changed_before { + println!("❌ -> {}", changed_after - changed_before); + } + } + + Ok(()) + } + + /// Walk the call to find the path to the main function + fn find_path_to_main(&self, name: &str) -> Vec { + let mut path = Vec::new(); + let mut visited = std::collections::HashSet::new(); + + // Helper function for DFS with backtracking + fn dfs( + current: &str, + path: &mut Vec, + visited: &mut std::collections::HashSet, + parents: &std::collections::HashMap>, + ) -> bool { + // If we've found main, we're done + if current.ends_with("_main") { + path.push(current.to_string()); + return true; + } + + // Mark current node as visited + visited.insert(current.to_string()); + path.push(current.to_string()); + + // Check all parents of the current node + if let Some(parent_nodes) = parents.get(current) { + for parent in parent_nodes { + if !visited.contains(parent) { + if dfs(parent, path, visited, parents) { + return true; + } + } + } + } + + // If no path is found through this node, backtrack + path.pop(); + + false + } + + // Start DFS from the given name + dfs(name, &mut path, &mut visited, &self.parents); + + path + } + + fn load_file(&mut self, name: &str) -> Result<()> { + let new = &self.new[name]; + let Some(old) = self.old.get(name) else { + self.modified_files.entry(new.path.clone()).or_default(); + return Ok(()); + }; + + let mut changed_list = HashSet::new(); + for section in new.file.sections() { + let n = section.name().unwrap(); + if n == "__text" + || n == "__const" + || n.starts_with("__literal") + || n == "__eh_frame" + || n == "__compact_unwind" + || n == "__gcc_except_tab" + || n == "__common" + || n == "__bss" + { + changed_list.extend(self.accumulate_changed(&old, &new, section.index())); + } else { + println!("Skipping section: {n}"); + } + } + + for c in changed_list.iter() { + if !c.starts_with("l") && !c.starts_with("ltmp") { + self.modified_symbols.insert(c.to_string()); + } else { + let mod_name = format!("{c}_{name}"); + self.modified_symbols.insert(mod_name); + } + } + + for (child, parents) in new.parents.iter() { + let child_name = match child.starts_with("l") { + true => format!("{child}_{name}"), + false => child.to_string(), + }; + + for parent in parents { + let p_name = match parent.starts_with("l") { + true => format!("{parent}_{name}"), + false => parent.to_string(), + }; + + self.parents + .entry(child_name.clone()) + .or_default() + .insert(p_name); + } + } + + Ok(()) + } + + fn accumulate_changed( + &self, + old: &LoadedFile, + new: &LoadedFile, + section_idx: SectionIndex, + ) -> HashSet<&'static str> { + let mut local_modified = HashSet::new(); + + // Accumulate modified symbols using masking in functions + let relocated_new = acc_symbols(&new.file, section_idx); + let mut relocated_old = acc_symbols(&old.file, section_idx) + .into_iter() + .map(|f| (f.name, f)) + .collect::>(); + + for right in relocated_new { + let Some(left) = relocated_old.remove(right.name) else { + local_modified.insert(right.name); + continue; + }; + + // If the contents of the assembly changed, track it + if !compare_masked(old.file, new.file, &left, &right) { + local_modified.insert(left.name); + local_modified.insert(right.name); + } + } + + local_modified + } +} + +/// A file loaded into memory with its analysis +/// +/// We leak the module to make it easier to deal with its contents +struct LoadedFile { + path: PathBuf, + open_file: std::fs::File, + mmap: &'static Mmap, + + file: &'static File<'static>, + + // symbol -> symbols + parents: HashMap<&'static str, HashSet<&'static str>>, +} + +impl LoadedFile { + fn from_dir(dir: &Path) -> anyhow::Result> { + std::fs::read_dir(dir)? + .into_iter() + .flatten() + .filter(|e| e.path().extension() == Some(OsStr::new("o"))) + .map(|e| { + Ok(( + e.path().file_name().unwrap().to_string_lossy().to_string(), + Self::new(e.path())?, + )) + }) + .collect() + } + + fn new(path: PathBuf) -> anyhow::Result { + let open_file = std::fs::File::open(&path)?; + let mmap = unsafe { MmapOptions::new().map(&open_file).unwrap() }; + let mmap: &'static Mmap = Box::leak(Box::new(mmap)); + let f = File::parse(mmap.deref() as &[u8])?; + let file: &'static File<'static> = Box::leak(Box::new(f)); + + // Set up the data structures + let mut sym_tab = HashMap::<&'static str, RelocatedSymbol<'static>>::new(); + let mut parents = HashMap::<&'static str, HashSet<&'static str>>::new(); + + // Build the symbol table + for sect in file.sections() { + for r in acc_symbols(&file, sect.index()) { + sym_tab.insert(r.name, r); + } + } + + // Create a map of address -> symbol so we can resolve the section of a symbol + let local_defs = file + .symbols() + .filter(|s| s.is_definition()) + .map(|s| (s.address(), s.name().unwrap())) + .collect::>(); + + // Build the call graph by walking the relocations + // We keep track of what calls whata + for (sym_name, sym) in sym_tab.iter() { + let sym_section = file.section_by_index(sym.section).unwrap(); + let sym_data = sym_section.data().unwrap(); + + for (addr, reloc) in sym.relocations.iter() { + let target = match symbol_name_of_relo(file, reloc.target()) { + Some(name) => name, + None => { + let addend = u64::from_le_bytes( + sym_data[*addr as usize..(*addr + 8) as usize] + .try_into() + .unwrap(), + ); + local_defs.get(&addend).unwrap() + } + }; + + parents.entry(target).or_default().insert(sym_name); + } + } + + Ok(Self { + path, + open_file, + mmap, + file, + parents, + }) + } +} + +/// A function with its relevant relocations to be used for masked comparisons +struct RelocatedSymbol<'a> { + name: &'a str, + /// offset within the section + offset: usize, + data: &'a [u8], + relocations: &'a [(u64, ReadRelocation)], + sym: object::Symbol<'a, 'a>, + section: SectionIndex, +} + +fn acc_symbols<'a>(new: &'a File<'a>, section_idx: SectionIndex) -> Vec> { + let mut syms = vec![]; + + let section = new.section_by_index(section_idx).unwrap(); + + let sorted = new + .symbols() + .filter(|s| s.section_index() == Some(section_idx)) + .sorted_by(|a, b| { + let addr = a.address().cmp(&b.address()); + if addr == Ordering::Equal { + a.index().0.cmp(&b.index().0) + } else { + addr + } + }) + .collect::>(); + + // todo!!!!!! jon: don't leak this lol + let relocations = section + .relocations() + .sorted_by(|a, b| a.0.cmp(&b.0).reverse()) + .collect::>() + .leak(); + + let data = section.data().unwrap(); + + // No symbols, no symbols, + if sorted.is_empty() { + println!("No symbols for section: {:?}", section.name()); + return vec![]; + } + + // The end of the currently analyzed function + let mut func_end = section.size() as usize; + + // The idx into the relocation list that applies to this function. We'll march these + let mut reloc_idx = 0; + + // Walk in reverse so we can use the text_length as the initial backstop and to match relocation order + for sym in sorted.into_iter().rev() { + let sym_offset = sym.address() - section.address(); + + // Move the head/tail to include the sub-slice of the relocations that apply to this symbol + let mut reloc_start = None; + loop { + // If we've reached the end of the relocations then we're done + if reloc_idx == relocations.len() { + break; + } + + // relocations behind the symbol start don't apply + if relocations[reloc_idx].0 < sym_offset { + break; + } + + // Set the head to the first relocation that applies + if reloc_start.is_none() { + reloc_start = Some(reloc_idx); + } + + reloc_idx += 1; + } + + // Identify the instructions that apply to this symbol + let data = match reloc_start { + Some(_start) => &data[sym_offset as usize..func_end], + _ => &[], + }; + + // Identify the relocations that apply to this symbol + let relocations = match reloc_start { + Some(start) => &relocations[start..reloc_idx], + None => &[], + }; + + syms.push(RelocatedSymbol { + name: sym.name().unwrap(), + sym, + offset: sym_offset as usize, + data, + relocations, + section: section_idx, + }); + + func_end = sym_offset as usize; + } + + assert_eq!(reloc_idx, relocations.len()); + + syms +} + +/// Compare two sets of bytes, masking out the bytes that are not part of the symbol +/// This is so we can compare functions with different relocations +fn compare_masked<'a>( + old: &impl Object<'a>, + new: &impl Object<'a>, + left: &RelocatedSymbol, + right: &RelocatedSymbol, +) -> bool { + // Make sure the relocations are the same length + if left.relocations.len() != right.relocations.len() { + return false; + } + + // Make sure the data is the same length + // If the size changed then the instructions are different (well, not necessarily, but enough) + if left.data.len() != right.data.len() { + return false; + } + + // Make sure the names match + if left.name != right.name { + return false; + } + + // We're going to walk from relocation target to target, but since there's no implicit target + // to start with, we simply use the end of the data + let mut last = left.data.len(); + + // Ensure the relocations point to the same symbol + // Data symbols are special ... todo + // + // relocations are in reverse order, so we can also compare the data as we go + for x in 0..left.relocations.len() { + // Grab the reloc + let (l_addr, left_reloc): &(u64, ReadRelocation) = &left.relocations[x]; + let (_r_addr, right_reloc): &(u64, ReadRelocation) = &right.relocations[x]; + + // The targets might not be same by index but should resolve to the same *name* + let left_target: RelocationTarget = left_reloc.target(); + let right_target: RelocationTarget = right_reloc.target(); + + // Use the name of the symbol to compare + // todo: decide if it's internal vs external + let left_name = symbol_name_of_relo(old, left_target); + let right_name = symbol_name_of_relo(new, right_target); + let (Some(left_name), Some(right_name)) = (left_name, right_name) else { + continue; + }; + + // Make sure the names match + // if the target is a locally defined symbol, then it might be the same + // todo(jon): hash the masked contents + if left_name != right_name { + return false; + } + + // Check the data + // the slice is the end of the relocation to the start of the previous relocation + let reloc_byte_size = (left_reloc.size() as usize) / 8; + let start = *l_addr as usize - left.offset as usize + reloc_byte_size; + + // Some relocations target the same location + // In these cases, we just continue since we just masked and checked them already + if (*l_addr as usize - left.offset as usize) == last { + continue; + } + + debug_assert!(start <= last); + debug_assert!(start <= left.data.len()); + + if &left.data[start..last] != &right.data[start..last] { + return false; + } + + if left_reloc.flags() != right_reloc.flags() { + return false; + } + + // todo: more checking... the symbols might be local + last = start - reloc_byte_size; + } + + // And a final check to make sure the data is the same + if left.data[..last] != right.data[..last] { + return false; + } + + true +} + +fn symbol_name_of_relo<'a>(obj: &impl Object<'a>, target: RelocationTarget) -> Option<&'a str> { + match target { + RelocationTarget::Symbol(symbol_index) => Some( + obj.symbol_by_index(symbol_index) + .unwrap() + .name_bytes() + .unwrap() + .to_utf8(), + ), + RelocationTarget::Section(_) => None, + RelocationTarget::Absolute => None, + _ => None, + } +} + +fn workspace_dir() -> PathBuf { + "/Users/jonkelley/Development/Tinkering/ipbp".into() +} + +trait ToUtf8<'a> { + fn to_utf8(&self) -> &'a str; +} + +impl<'a> ToUtf8<'a> for &'a [u8] { + fn to_utf8(&self) -> &'a str { + std::str::from_utf8(self).unwrap() + } +} + +/// Builds an object file that satisfies the imports +/// +/// Creates stub functions that jump to known addresses in a target process. +/// +/// .section __TEXT,__text +/// .globl __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E +/// .p2align 2 +/// __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E: +/// // Load 64-bit address using immediate values +/// movz x9, #0xCDEF // Bottom 16 bits +/// movk x9, #0x89AB, lsl #16 // Next 16 bits +/// movk x9, #0x4567, lsl #32 // Next 16 bits +/// movk x9, #0x0123, lsl #48 // Top 16 bits +/// +/// // Branch to the loaded address +/// br x9 +fn build_stub( + format: BinaryFormat, + architecture: Architecture, + endian: Endianness, + adrp_imports: HashMap<&str, u64>, +) -> Result> { + use object::{ + write::{Object, Symbol, SymbolSection}, + SectionKind, SymbolFlags, SymbolKind, SymbolScope, + }; + + // Create a new ARM64 object file + let mut obj = Object::new(format, architecture, endian); + + // Add a text section for our trampolines + let text_section = obj.add_section(Vec::new(), ".text".into(), SectionKind::Text); + + for (name, addr) in adrp_imports { + // Add the symbol + obj.add_symbol(Symbol { + name: name.into(), + value: addr, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + } + + obj.write().context("Failed to write object file") +} + +fn make_stub_file( + proc_main_addr: u64, + patch_target: PathBuf, + adrp_imports: HashSet<&str>, +) -> Vec { + let data = fs::read(&patch_target).unwrap(); + let old = File::parse(&data as &[u8]).unwrap(); + let main_sym = old.symbol_by_name_bytes(b"_main").unwrap(); + let aslr_offset = proc_main_addr - main_sym.address(); + let addressed = old + .symbols() + .filter_map(|sym| { + adrp_imports + .get(sym.name().ok()?) + .copied() + .map(|o| (o, sym.address() + aslr_offset)) + }) + .collect::>(); + + build_stub( + old.format(), + old.architecture(), + old.endianness(), + addressed, + ) + .unwrap() +} + +// /// Move all previous object files to "incremental-old" and all new object files to "incremental-new" +// fn cache_incrementals(object_files: &[&String]) { +// let old = subsecond_folder().join("data").join("incremental-old"); +// let new = subsecond_folder().join("data").join("incremental-new"); + +// // Remove the old incremental-old directory if it exists +// _ = std::fs::remove_dir_all(&old); + +// // Rename incremental-new to incremental-old if it exists. Faster than moving all the files +// _ = std::fs::rename(&new, &old); + +// // Create the new incremental-new directory to place the outputs in +// std::fs::create_dir_all(&new).unwrap(); + +// // Now drop in all the new object files +// for o in object_files.iter() { +// if !o.ends_with(".rcgu.o") { +// continue; +// } + +// let path = PathBuf::from(o); +// std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); +// } +// } diff --git a/packages/subsecond/subsecond-cli-support/src/partial.rs b/packages/subsecond/subsecond-cli-support/src/partial.rs deleted file mode 100644 index 372a49b765..0000000000 --- a/packages/subsecond/subsecond-cli-support/src/partial.rs +++ /dev/null @@ -1,679 +0,0 @@ -use anyhow::{Context, Result}; -use itertools::Itertools; -use memmap::{Mmap, MmapOptions}; -use object::{ - read::File, Architecture, BinaryFormat, Endianness, Object, ObjectSection, ObjectSymbol, - Relocation, RelocationTarget, SectionIndex, -}; -use std::{cmp::Ordering, ffi::OsStr, fs, ops::Deref, path::PathBuf}; -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - path::Path, -}; -pub use subsecond_types::*; -use target_lexicon::{OperatingSystem, Triple}; -use tokio::process::Command; - -async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, out_path: PathBuf) { - let mut object = ObjectDiff::new().unwrap(); - object.load().unwrap(); - let diff = object.diff().unwrap(); - - // Assemble the stub - let stub_data = make_stub_file(proc_main_addr, patch_target, diff.adrp_imports); - let stub_file = workspace_dir().join("stub.o"); - std::fs::write(&stub_file, stub_data).unwrap(); -} - -struct ObjectDiffResult<'a> { - adrp_imports: HashSet<&'a str>, - modified_files: Vec<(&'a PathBuf, &'a HashSet)>, - modified_symbols: HashSet<&'a String>, -} - -struct ObjectDiff { - old: BTreeMap, - new: BTreeMap, - modified_files: HashMap>, - modified_symbols: HashSet, - parents: HashMap>, -} - -impl ObjectDiff { - fn new() -> Result { - Ok(Self { - old: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-old"))?, - new: LoadedFile::from_dir(&workspace_dir().join("data").join("incremental-new"))?, - modified_files: Default::default(), - modified_symbols: Default::default(), - parents: Default::default(), - }) - } - - fn diff(&self) -> Result> { - let all_exports = self - .new - .iter() - .flat_map(|(_, f)| f.file.exports().unwrap()) - .map(|e| e.name().to_utf8()) - .collect::>(); - - let mut adrp_imports = HashSet::new(); - - let mut satisfied_exports = HashSet::new(); - - let modified_symbols = self.modified_symbols.iter().collect::>(); - - if modified_symbols.is_empty() { - println!("No modified symbols"); - } - - let mut modified_log = String::new(); - for m in modified_symbols.iter() { - let path = self.find_path_to_main(m); - modified_log.push_str(&format!("{m}\n")); - modified_log.push_str(&format!("{path:#?}\n")); - } - std::fs::write(workspace_dir().join("modified_symbols.txt"), modified_log).unwrap(); - - let modified = self - .modified_files - .iter() - .sorted_by(|a, b| a.0.cmp(&b.0)) - .collect::>(); - - // Figure out which symbols are required from *existing* code - // We're going to create a stub `.o` file that satisfies these by jumping into the original code via a dynamic lookup / and or literally just manually doing it - for fil in modified.iter() { - let f = self - .new - .get(fil.0.file_name().unwrap().to_str().unwrap()) - .unwrap(); - - for i in f.file.imports().unwrap() { - if all_exports.contains(i.name().to_utf8()) { - adrp_imports.insert(i.name().to_utf8()); - } - } - - for e in f.file.exports().unwrap() { - satisfied_exports.insert(e.name().to_utf8()); - } - } - - // Remove any imports that are indeed satisifed - for s in satisfied_exports.iter() { - adrp_imports.remove(s); - } - - Ok(ObjectDiffResult { - adrp_imports, - modified_files: modified, - modified_symbols, - }) - } - - fn load(&mut self) -> Result<()> { - let num_right = self.new.len(); - - let keys = self.new.keys().cloned().collect::>(); - for (idx, f) in keys.iter().enumerate() { - println!("----- {:?} {}/{} -----", f, idx, num_right); - - let changed_before = self.modified_symbols.len(); - self.load_file(f)?; - let changed_after = self.modified_symbols.len(); - - if changed_after > changed_before { - println!("❌ -> {}", changed_after - changed_before); - } - } - - Ok(()) - } - - /// Walk the call to find the path to the main function - fn find_path_to_main(&self, name: &str) -> Vec { - let mut path = Vec::new(); - let mut visited = std::collections::HashSet::new(); - - // Helper function for DFS with backtracking - fn dfs( - current: &str, - path: &mut Vec, - visited: &mut std::collections::HashSet, - parents: &std::collections::HashMap>, - ) -> bool { - // If we've found main, we're done - if current.ends_with("_main") { - path.push(current.to_string()); - return true; - } - - // Mark current node as visited - visited.insert(current.to_string()); - path.push(current.to_string()); - - // Check all parents of the current node - if let Some(parent_nodes) = parents.get(current) { - for parent in parent_nodes { - if !visited.contains(parent) { - if dfs(parent, path, visited, parents) { - return true; - } - } - } - } - - // If no path is found through this node, backtrack - path.pop(); - - false - } - - // Start DFS from the given name - dfs(name, &mut path, &mut visited, &self.parents); - - path - } - - fn load_file(&mut self, name: &str) -> Result<()> { - let new = &self.new[name]; - let Some(old) = self.old.get(name) else { - self.modified_files.entry(new.path.clone()).or_default(); - return Ok(()); - }; - - let mut changed_list = HashSet::new(); - for section in new.file.sections() { - let n = section.name().unwrap(); - if n == "__text" - || n == "__const" - || n.starts_with("__literal") - || n == "__eh_frame" - || n == "__compact_unwind" - || n == "__gcc_except_tab" - || n == "__common" - || n == "__bss" - { - changed_list.extend(self.accumulate_changed(&old, &new, section.index())); - } else { - println!("Skipping section: {n}"); - } - } - - for c in changed_list.iter() { - if !c.starts_with("l") && !c.starts_with("ltmp") { - self.modified_symbols.insert(c.to_string()); - } else { - let mod_name = format!("{c}_{name}"); - self.modified_symbols.insert(mod_name); - } - } - - for (child, parents) in new.parents.iter() { - let child_name = match child.starts_with("l") { - true => format!("{child}_{name}"), - false => child.to_string(), - }; - - for parent in parents { - let p_name = match parent.starts_with("l") { - true => format!("{parent}_{name}"), - false => parent.to_string(), - }; - - self.parents - .entry(child_name.clone()) - .or_default() - .insert(p_name); - } - } - - Ok(()) - } - - fn accumulate_changed( - &self, - old: &LoadedFile, - new: &LoadedFile, - section_idx: SectionIndex, - ) -> HashSet<&'static str> { - let mut local_modified = HashSet::new(); - - // Accumulate modified symbols using masking in functions - let relocated_new = acc_symbols(&new.file, section_idx); - let mut relocated_old = acc_symbols(&old.file, section_idx) - .into_iter() - .map(|f| (f.name, f)) - .collect::>(); - - for right in relocated_new { - let Some(left) = relocated_old.remove(right.name) else { - local_modified.insert(right.name); - continue; - }; - - // If the contents of the assembly changed, track it - if !compare_masked(old.file, new.file, &left, &right) { - local_modified.insert(left.name); - local_modified.insert(right.name); - } - } - - local_modified - } -} - -/// A file loaded into memory with its analysis -/// -/// We leak the module to make it easier to deal with its contents -struct LoadedFile { - path: PathBuf, - open_file: std::fs::File, - mmap: &'static Mmap, - - file: &'static File<'static>, - - // symbol -> symbols - parents: HashMap<&'static str, HashSet<&'static str>>, -} - -impl LoadedFile { - fn from_dir(dir: &Path) -> anyhow::Result> { - std::fs::read_dir(dir)? - .into_iter() - .flatten() - .filter(|e| e.path().extension() == Some(OsStr::new("o"))) - .map(|e| { - Ok(( - e.path().file_name().unwrap().to_string_lossy().to_string(), - Self::new(e.path())?, - )) - }) - .collect() - } - - fn new(path: PathBuf) -> anyhow::Result { - let open_file = std::fs::File::open(&path)?; - let mmap = unsafe { MmapOptions::new().map(&open_file).unwrap() }; - let mmap: &'static Mmap = Box::leak(Box::new(mmap)); - let f = File::parse(mmap.deref() as &[u8])?; - let file: &'static File<'static> = Box::leak(Box::new(f)); - - // Set up the data structures - let mut sym_tab = HashMap::<&'static str, RelocatedSymbol<'static>>::new(); - let mut parents = HashMap::<&'static str, HashSet<&'static str>>::new(); - - // Build the symbol table - for sect in file.sections() { - for r in acc_symbols(&file, sect.index()) { - sym_tab.insert(r.name, r); - } - } - - // Create a map of address -> symbol so we can resolve the section of a symbol - let local_defs = file - .symbols() - .filter(|s| s.is_definition()) - .map(|s| (s.address(), s.name().unwrap())) - .collect::>(); - - // Build the call graph by walking the relocations - // We keep track of what calls whata - for (sym_name, sym) in sym_tab.iter() { - let sym_section = file.section_by_index(sym.section).unwrap(); - let sym_data = sym_section.data().unwrap(); - - for (addr, reloc) in sym.relocations.iter() { - let target = match symbol_name_of_relo(file, reloc.target()) { - Some(name) => name, - None => { - let addend = u64::from_le_bytes( - sym_data[*addr as usize..(*addr + 8) as usize] - .try_into() - .unwrap(), - ); - local_defs.get(&addend).unwrap() - } - }; - - parents.entry(target).or_default().insert(sym_name); - } - } - - Ok(Self { - path, - open_file, - mmap, - file, - parents, - }) - } -} - -/// A function with its relevant relocations to be used for masked comparisons -struct RelocatedSymbol<'a> { - name: &'a str, - /// offset within the section - offset: usize, - data: &'a [u8], - relocations: &'a [(u64, Relocation)], - sym: object::Symbol<'a, 'a>, - section: SectionIndex, -} - -fn acc_symbols<'a>(new: &'a File<'a>, section_idx: SectionIndex) -> Vec> { - let mut syms = vec![]; - - let section = new.section_by_index(section_idx).unwrap(); - - let sorted = new - .symbols() - .filter(|s| s.section_index() == Some(section_idx)) - .sorted_by(|a, b| { - let addr = a.address().cmp(&b.address()); - if addr == Ordering::Equal { - a.index().0.cmp(&b.index().0) - } else { - addr - } - }) - .collect::>(); - - // todo!!!!!! jon: don't leak this lol - let relocations = section - .relocations() - .sorted_by(|a, b| a.0.cmp(&b.0).reverse()) - .collect::>() - .leak(); - - let data = section.data().unwrap(); - - // No symbols, no symbols, - if sorted.is_empty() { - println!("No symbols for section: {:?}", section.name()); - return vec![]; - } - - // The end of the currently analyzed function - let mut func_end = section.size() as usize; - - // The idx into the relocation list that applies to this function. We'll march these - let mut reloc_idx = 0; - - // Walk in reverse so we can use the text_length as the initial backstop and to match relocation order - for sym in sorted.into_iter().rev() { - let sym_offset = sym.address() - section.address(); - - // Move the head/tail to include the sub-slice of the relocations that apply to this symbol - let mut reloc_start = None; - loop { - // If we've reached the end of the relocations then we're done - if reloc_idx == relocations.len() { - break; - } - - // relocations behind the symbol start don't apply - if relocations[reloc_idx].0 < sym_offset { - break; - } - - // Set the head to the first relocation that applies - if reloc_start.is_none() { - reloc_start = Some(reloc_idx); - } - - reloc_idx += 1; - } - - // Identify the instructions that apply to this symbol - let data = match reloc_start { - Some(_start) => &data[sym_offset as usize..func_end], - _ => &[], - }; - - // Identify the relocations that apply to this symbol - let relocations = match reloc_start { - Some(start) => &relocations[start..reloc_idx], - None => &[], - }; - - syms.push(RelocatedSymbol { - name: sym.name().unwrap(), - sym, - offset: sym_offset as usize, - data, - relocations, - section: section_idx, - }); - - func_end = sym_offset as usize; - } - - assert_eq!(reloc_idx, relocations.len()); - - syms -} - -/// Compare two sets of bytes, masking out the bytes that are not part of the symbol -/// This is so we can compare functions with different relocations -fn compare_masked<'a>( - old: &impl Object<'a>, - new: &impl Object<'a>, - left: &RelocatedSymbol, - right: &RelocatedSymbol, -) -> bool { - // Make sure the relocations are the same length - if left.relocations.len() != right.relocations.len() { - return false; - } - - // Make sure the data is the same length - // If the size changed then the instructions are different (well, not necessarily, but enough) - if left.data.len() != right.data.len() { - return false; - } - - // Make sure the names match - if left.name != right.name { - return false; - } - - // We're going to walk from relocation target to target, but since there's no implicit target - // to start with, we simply use the end of the data - let mut last = left.data.len(); - - // Ensure the relocations point to the same symbol - // Data symbols are special ... todo - // - // relocations are in reverse order, so we can also compare the data as we go - for x in 0..left.relocations.len() { - // Grab the reloc - let (l_addr, left_reloc): &(u64, Relocation) = &left.relocations[x]; - let (_r_addr, right_reloc): &(u64, Relocation) = &right.relocations[x]; - - // The targets might not be same by index but should resolve to the same *name* - let left_target: RelocationTarget = left_reloc.target(); - let right_target: RelocationTarget = right_reloc.target(); - - // Use the name of the symbol to compare - // todo: decide if it's internal vs external - let left_name = symbol_name_of_relo(old, left_target); - let right_name = symbol_name_of_relo(new, right_target); - let (Some(left_name), Some(right_name)) = (left_name, right_name) else { - continue; - }; - - // Make sure the names match - // if the target is a locally defined symbol, then it might be the same - // todo(jon): hash the masked contents - if left_name != right_name { - return false; - } - - // Check the data - // the slice is the end of the relocation to the start of the previous relocation - let reloc_byte_size = (left_reloc.size() as usize) / 8; - let start = *l_addr as usize - left.offset as usize + reloc_byte_size; - - // Some relocations target the same location - // In these cases, we just continue since we just masked and checked them already - if (*l_addr as usize - left.offset as usize) == last { - continue; - } - - debug_assert!(start <= last); - debug_assert!(start <= left.data.len()); - - if &left.data[start..last] != &right.data[start..last] { - return false; - } - - if left_reloc.flags() != right_reloc.flags() { - return false; - } - - // todo: more checking... the symbols might be local - last = start - reloc_byte_size; - } - - // And a final check to make sure the data is the same - if left.data[..last] != right.data[..last] { - return false; - } - - true -} - -fn symbol_name_of_relo<'a>(obj: &impl Object<'a>, target: RelocationTarget) -> Option<&'a str> { - match target { - RelocationTarget::Symbol(symbol_index) => Some( - obj.symbol_by_index(symbol_index) - .unwrap() - .name_bytes() - .unwrap() - .to_utf8(), - ), - RelocationTarget::Section(_) => None, - RelocationTarget::Absolute => None, - _ => None, - } -} - -fn workspace_dir() -> PathBuf { - "/Users/jonkelley/Development/Tinkering/ipbp".into() -} - -trait ToUtf8<'a> { - fn to_utf8(&self) -> &'a str; -} - -impl<'a> ToUtf8<'a> for &'a [u8] { - fn to_utf8(&self) -> &'a str { - std::str::from_utf8(self).unwrap() - } -} - -/// Builds an object file that satisfies the imports -/// -/// Creates stub functions that jump to known addresses in a target process. -/// -/// .section __TEXT,__text -/// .globl __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E -/// .p2align 2 -/// __ZN4core3fmt3num52_$LT$impl$u20$core..fmt..Debug$u20$for$u20$usize$GT$3fmt17h4e710f94be547818E: -/// // Load 64-bit address using immediate values -/// movz x9, #0xCDEF // Bottom 16 bits -/// movk x9, #0x89AB, lsl #16 // Next 16 bits -/// movk x9, #0x4567, lsl #32 // Next 16 bits -/// movk x9, #0x0123, lsl #48 // Top 16 bits -/// -/// // Branch to the loaded address -/// br x9 -fn build_stub( - format: BinaryFormat, - architecture: Architecture, - endian: Endianness, - adrp_imports: HashMap<&str, u64>, -) -> Result> { - use object::{ - write::{Object, Symbol, SymbolSection}, - SectionKind, SymbolFlags, SymbolKind, SymbolScope, - }; - - // Create a new ARM64 object file - let mut obj = Object::new(format, architecture, endian); - - // Add a text section for our trampolines - let text_section = obj.add_section(Vec::new(), ".text".into(), SectionKind::Text); - - for (name, addr) in adrp_imports { - // Add the symbol - obj.add_symbol(Symbol { - name: name.into(), - value: addr, - size: 0, - kind: SymbolKind::Text, - scope: SymbolScope::Dynamic, - weak: false, - section: SymbolSection::Section(text_section), - flags: SymbolFlags::None, - }); - } - - obj.write().context("Failed to write object file") -} - -fn make_stub_file( - proc_main_addr: u64, - patch_target: PathBuf, - adrp_imports: HashSet<&str>, -) -> Vec { - let data = fs::read(&patch_target).unwrap(); - let old = File::parse(&data as &[u8]).unwrap(); - let main_sym = old.symbol_by_name_bytes(b"_main").unwrap(); - let aslr_offset = proc_main_addr - main_sym.address(); - let addressed = old - .symbols() - .filter_map(|sym| { - adrp_imports - .get(sym.name().ok()?) - .copied() - .map(|o| (o, sym.address() + aslr_offset)) - }) - .collect::>(); - - build_stub( - old.format(), - old.architecture(), - old.endianness(), - addressed, - ) - .unwrap() -} - -// /// Move all previous object files to "incremental-old" and all new object files to "incremental-new" -// fn cache_incrementals(object_files: &[&String]) { -// let old = subsecond_folder().join("data").join("incremental-old"); -// let new = subsecond_folder().join("data").join("incremental-new"); - -// // Remove the old incremental-old directory if it exists -// _ = std::fs::remove_dir_all(&old); - -// // Rename incremental-new to incremental-old if it exists. Faster than moving all the files -// _ = std::fs::rename(&new, &old); - -// // Create the new incremental-new directory to place the outputs in -// std::fs::create_dir_all(&new).unwrap(); - -// // Now drop in all the new object files -// for o in object_files.iter() { -// if !o.ends_with(".rcgu.o") { -// continue; -// } - -// let path = PathBuf::from(o); -// std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); -// } -// } diff --git a/packages/subsecond/subsecond-cli/src/index.html b/packages/subsecond/subsecond-cli/src/index.html index db6c10a79d..f8ff041c01 100644 --- a/packages/subsecond/subsecond-cli/src/index.html +++ b/packages/subsecond/subsecond-cli/src/index.html @@ -5,74 +5,6 @@ Subsecond diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 7b15d234ed..ba200460a4 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -272,7 +272,7 @@ impl AppBuilder { mode: BuildMode::Thin { changed_files, direct_rustc: self.artifacts.as_ref().unwrap().direct_rustc.clone(), - aslr_reference: self.aslr_reference.unwrap(), + aslr_reference: self.aslr_reference, }, }; async move { request.build(&ctx).await } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index fda94ba45e..381e1b6d37 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -340,7 +340,7 @@ pub enum BuildMode { Thin { direct_rustc: RustcArgs, changed_files: Vec, - aslr_reference: u64, + aslr_reference: Option, }, } @@ -995,7 +995,7 @@ impl BuildRequest { async fn write_patch( &self, ctx: &BuildContext, - aslr_reference: u64, + aslr_reference: Option, time_start: SystemTime, ) -> Result<()> { tracing::debug!("Patching existing bundle"); @@ -1020,7 +1020,7 @@ impl BuildRequest { &orig_exe, &object_files, &self.triple, - aslr_reference, + aslr_reference.context("ASLR reference not found - is the client connected?")?, ) .expect("failed to resolve patch symbols"); let patch_file = self.main_exe().with_file_name("patch-syms.o"); @@ -1048,7 +1048,7 @@ impl BuildRequest { Platform::Windows => PathBuf::from("cc"), }; - let thin_args = self.thin_link_args(&args, aslr_reference)?; + let thin_args = self.thin_link_args(&args)?; // let mut env_vars = vec![]; // self.build_android_env(&mut env_vars, false)?; @@ -1096,7 +1096,7 @@ impl BuildRequest { Ok(()) } - fn thin_link_args(&self, original_args: &[&str], aslr_reference: u64) -> Result> { + fn thin_link_args(&self, original_args: &[&str]) -> Result> { use target_lexicon::OperatingSystem; let triple = self.triple.clone(); diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index bd076dd818..8775968b6c 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -423,7 +423,8 @@ impl AppRunner { self.clear_hot_reload_changes(); // self.clear_cached_rsx(); - server.start_patch().await + server.start_patch().await; + server.send_patch_start().await; } else { let msg = HotReloadMsg { templates, diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index dd304812d9..2170d74d87 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -303,6 +303,12 @@ impl WebServer { self.send_devserver_message_to_all(msg).await; } + /// Tells all clients that a hot patch has started. + pub(crate) async fn send_patch_start(&mut self) { + self.send_devserver_message_to_all(DevserverMsg::HotPatchStart) + .await; + } + /// Tells all clients that a full rebuild has started. pub(crate) async fn send_reload_start(&mut self) { self.send_devserver_message_to_all(DevserverMsg::FullReloadStart) diff --git a/packages/devtools-types/src/lib.rs b/packages/devtools-types/src/lib.rs index e2c091a50d..b0a4be9c28 100644 --- a/packages/devtools-types/src/lib.rs +++ b/packages/devtools-types/src/lib.rs @@ -11,6 +11,9 @@ pub enum DevserverMsg { /// This includes all the templates/literals/assets/binary patches that have changed in one shot HotReload(HotReloadMsg), + /// Starting a hotpatch + HotPatchStart, + /// The devserver is starting a full rebuild. FullReloadStart, diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index e5a840d5e4..b65fd0d54b 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -188,6 +188,7 @@ async fn serve_server( DevserverMsg::FullReloadFailed => {} DevserverMsg::FullReloadCommand => {} DevserverMsg::Shutdown => {} + _ => {} } } Msg::TcpStream(Err(_)) => {} diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index 165c45cd6c..666b6abcca 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -83,16 +83,24 @@ fn make_ws( // The devserver is telling us that it started a full rebuild. This does not mean that it is ready. Ok(DevserverMsg::FullReloadStart) => show_toast( - runtime_.clone(), "Your app is being rebuilt.", "A non-hot-reloadable change occurred and we must rebuild.", ToastLevel::Info, TOAST_TIMEOUT_LONG, false, ), + + // The devserver is telling us that it started a full rebuild. This does not mean that it is ready. + Ok(DevserverMsg::HotPatchStart) => show_toast( + "Hot-patching app...", + "Rust code changed and we are hot-patching.", + ToastLevel::Info, + TOAST_TIMEOUT_LONG, + false, + ), + // The devserver is telling us that the full rebuild failed. Ok(DevserverMsg::FullReloadFailed) => show_toast( - runtime_.clone(), "Oops! The build failed.", "We tried to rebuild your app, but something went wrong.", ToastLevel::Error, @@ -103,7 +111,6 @@ fn make_ws( // The devserver is telling us to reload the whole page Ok(DevserverMsg::FullReloadCommand) => { show_toast( - runtime_.clone(), "Successfully rebuilt.", "Your app was rebuilt successfully and without error.", ToastLevel::Success, @@ -116,6 +123,8 @@ fn make_ws( Err(e) => web_sys::console::error_1( &format!("Error parsing devserver message: {}", e).into(), ), + + _ => {} } }) .into_js_value() @@ -192,7 +201,7 @@ fn make_ws( } /// Represents what color the toast should have. -enum ToastLevel { +pub enum ToastLevel { /// Green Success, /// Blue @@ -211,9 +220,18 @@ impl Display for ToastLevel { } } +pub fn close_toast() { + js_sys::eval( + r#" + if (typeof closeDXToast !== "undefined") { + window.closeDXToast(); + } + "#, + ); +} + /// Displays a toast to the developer. -fn show_toast( - runtime: Rc, +pub fn show_toast( header_text: &str, message: &str, level: ToastLevel, @@ -227,22 +245,15 @@ fn show_toast( false => "showDXToast", }; - // #[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#" - // console.log("hello"); - // "#)] - // pub fn show_test() {} - // Create the guard before running eval which uses the global runtime context - let _guard = RuntimeGuard::new(runtime); - ScopeId::ROOT.in_runtime(|| { - eval(&format!( - r#" + + js_sys::eval(&format!( + r#" if (typeof {js_fn_name} !== "undefined") {{ - {js_fn_name}("{header_text}", "{message}", "{level}", {as_ms}); + window.{js_fn_name}("{header_text}", "{message}", "{level}", {as_ms}); }} "#, - )); - }); + )); } /// Force a hotreload of the assets on this page by walking them and changing their URLs to include diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 9bcf5d3c1e..21f25b9a09 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -206,6 +206,8 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { if !hr_msg.assets.is_empty() { crate::devtools::invalidate_browser_asset_cache(); } + + devtools::close_toast(); } #[cfg(feature = "hydrate")] From 67ec00088b9e46b39d8d455447eb7117048e7d64 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 18:23:31 -0700 Subject: [PATCH 125/301] tiny bit more robust --- .../fullstack-hello-world/src/main.rs | 4 +--- packages/cli/assets/web/dev.index.html | 19 ++++++++++++------- packages/cli/src/serve/mod.rs | 4 +++- packages/cli/src/serve/server.rs | 4 +++- packages/devtools-types/src/lib.rs | 1 + packages/devtools/src/lib.rs | 8 +++++--- packages/web/src/lib.rs | 18 +++++++++++++----- 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/example-projects/fullstack-hello-world/src/main.rs b/example-projects/fullstack-hello-world/src/main.rs index acfdfebd31..e5142f1465 100644 --- a/example-projects/fullstack-hello-world/src/main.rs +++ b/example-projects/fullstack-hello-world/src/main.rs @@ -14,8 +14,6 @@ fn app() -> Element { let mut text = use_signal(|| "...".to_string()); rsx! { - h1 { "Hot patch serverfns!" } - h1 { "Hot patch serverfns!" } h1 { "Hot patch serverfns!" } button { onclick: move |_| async move { @@ -29,5 +27,5 @@ fn app() -> Element { #[server] async fn say_hi() -> Result { - Ok("Hello from the server!".to_string()) + Ok("DUAL PATCHING ACHIEVED!".to_string()) } diff --git a/packages/cli/assets/web/dev.index.html b/packages/cli/assets/web/dev.index.html index 5f780b2924..e2a9ee9662 100644 --- a/packages/cli/assets/web/dev.index.html +++ b/packages/cli/assets/web/dev.index.html @@ -165,13 +165,18 @@

Your app is being rebuilt.

currentToast.addEventListener("click", closeDXToast); // Wait a bit of time so animation plays correctly. - setTimeout(() => { - innerElem.style.right = "0"; - - currentTimeout = setTimeout(() => { - closeDXToast(); - }, durationMs); - }, 100); + setTimeout( + () => { + let ourToast = currentToast; + innerElem.style.right = "0"; + currentTimeout = setTimeout(() => { + if (ourToast == currentToast) { + closeDXToast(); + } + }, durationMs); + }, + 100 + ); } // Schedule a toast to be displayed after reload. diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 7089ff6023..0450681165 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -137,8 +137,10 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { match bundle.mode { BuildMode::Thin { .. } => { // We need to patch the app with the new bundle + let elapsed = + bundle.time_end.duration_since(bundle.time_start).unwrap(); match builder.patch(&bundle).await { - Ok(jumptable) => devserver.send_patch(jumptable).await, + Ok(jumptable) => devserver.send_patch(jumptable, elapsed).await, Err(_) => {} } } diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 2170d74d87..973156ebc4 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -36,6 +36,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}, path::{Path, PathBuf}, sync::{Arc, RwLock}, + time::Duration, }; use subsecond_cli_support::JumpTable; use tokio::process::Command; @@ -295,9 +296,10 @@ impl WebServer { } } - pub(crate) async fn send_patch(&mut self, jump_table: JumpTable) { + pub(crate) async fn send_patch(&mut self, jump_table: JumpTable, time_taken: Duration) { let msg = DevserverMsg::HotReload(HotReloadMsg { jump_table: Some(jump_table), + ms_elapsed: time_taken.as_millis() as u64, ..Default::default() }); self.send_devserver_message_to_all(msg).await; diff --git a/packages/devtools-types/src/lib.rs b/packages/devtools-types/src/lib.rs index b0a4be9c28..2a457ef70f 100644 --- a/packages/devtools-types/src/lib.rs +++ b/packages/devtools-types/src/lib.rs @@ -48,6 +48,7 @@ pub struct HotReloadMsg { pub jump_table: Option, pub templates: Vec, pub assets: Vec, + pub ms_elapsed: u64, } impl HotReloadMsg { diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index ef16de2f9c..488ead66af 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -27,7 +27,7 @@ impl Devtools { /// Applies template and literal changes to the VirtualDom /// /// Assets need to be handled by the renderer. -pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { +pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), subsecond::PatchError> { dom.runtime().on_scope(ScopeId::ROOT, || { // 1. Update signals... let ctx = dioxus_signals::get_global_context(); @@ -50,10 +50,12 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) { // 2. Attempt to hotpatch if let Some(jump_table) = msg.jump_table.as_ref().cloned() { - unsafe { subsecond::apply_patch(jump_table) }; + unsafe { subsecond::apply_patch(jump_table) }?; dioxus_core::prelude::force_all_dirty(); } - }); + + Ok(()) + }) } pub fn apply_patch(table: JumpTable) -> Result<(), subsecond::PatchError> { diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 21f25b9a09..b82871f32b 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -20,6 +20,8 @@ //! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide //! validation of websys-specific features and not the general use of Dioxus. +use std::time::Duration; + pub use crate::cfg::Config; use crate::hydration::SuspenseMessage; use dioxus_core::VirtualDom; @@ -201,13 +203,19 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { #[cfg(all(feature = "devtools", debug_assertions))] if let Some(hr_msg) = hotreload_msg { // Replace all templates - dioxus_devtools::apply_changes(&virtual_dom, &hr_msg); + if dioxus_devtools::apply_changes(&virtual_dom, &hr_msg).is_ok() { + if !hr_msg.assets.is_empty() { + crate::devtools::invalidate_browser_asset_cache(); + } - if !hr_msg.assets.is_empty() { - crate::devtools::invalidate_browser_asset_cache(); + devtools::show_toast( + "Hot-patch success!", + &format!("App successfully patched in {} ms", hr_msg.ms_elapsed), + devtools::ToastLevel::Success, + Duration::from_millis(500), + false, + ); } - - devtools::close_toast(); } #[cfg(feature = "hydrate")] From ebf328b79f4cf6bd5729b228cd59e9ed321a5110 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 23:03:32 -0700 Subject: [PATCH 126/301] lil bit of clean up --- .../fullstack-hello-world/src/main.rs | 17 ++++++++++++++++- packages/devtools-types/src/lib.rs | 2 +- packages/logger/src/lib.rs | 1 - packages/server/src/launch.rs | 12 +++--------- packages/server/src/rt.rs | 19 +++++++++---------- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/example-projects/fullstack-hello-world/src/main.rs b/example-projects/fullstack-hello-world/src/main.rs index e5142f1465..84f1bc3d77 100644 --- a/example-projects/fullstack-hello-world/src/main.rs +++ b/example-projects/fullstack-hello-world/src/main.rs @@ -27,5 +27,20 @@ fn app() -> Element { #[server] async fn say_hi() -> Result { - Ok("DUAL PATCHING ACHIEVED!".to_string()) + Ok("DUAL asdasd ACHIEVED?????!".to_string()) +} + +#[server] +async fn say_bye() -> Result { + Ok("goodbye!".to_string()) +} + +#[server] +async fn say_bye2() -> Result { + Ok("goodbye1!".to_string()) +} + +#[server] +async fn say_bye3() -> Result { + Ok("goodbye2!".to_string()) } diff --git a/packages/devtools-types/src/lib.rs b/packages/devtools-types/src/lib.rs index 2a457ef70f..8b2f9ae396 100644 --- a/packages/devtools-types/src/lib.rs +++ b/packages/devtools-types/src/lib.rs @@ -45,10 +45,10 @@ pub enum ClientMsg { #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct HotReloadMsg { - pub jump_table: Option, pub templates: Vec, pub assets: Vec, pub ms_elapsed: u64, + pub jump_table: Option, } impl HotReloadMsg { diff --git a/packages/logger/src/lib.rs b/packages/logger/src/lib.rs index 1e7318a7eb..df78c8b567 100644 --- a/packages/logger/src/lib.rs +++ b/packages/logger/src/lib.rs @@ -4,7 +4,6 @@ use tracing::{ }; pub use tracing; -pub use tracing::{debug, error, info, trace, warn}; /// Attempt to initialize the subscriber if it doesn't already exist, with default settings. /// diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index b65fd0d54b..f198c0dc60 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -28,8 +28,8 @@ use tower::ServiceExt as _; // use tower::{Service, ServiceExt}; use crate::{ - register_server_fn_on_router, render_handler, rt::DioxusRouterExt, RenderHandleState, SSRState, - ServeConfig, ServeConfigBuilder, + collect_raw_server_fns, register_server_fn_on_router, render_handler, rt::DioxusRouterExt, + RenderHandleState, SSRState, ServeConfig, ServeConfigBuilder, }; type ContextList = Vec Box + Send + Sync>>; @@ -153,7 +153,7 @@ async fn serve_server( } for (_, f) in server_fn_map { - tracing::info!( + tracing::debug!( "Registering server function: {:?} {:?}", f.path(), f.method() @@ -260,12 +260,6 @@ async fn serve_server( } } -pub type AxumServerFn = ServerFnTraitObj, http::Response>; - -pub fn collect_raw_server_fns() -> Vec<&'static AxumServerFn> { - inventory::iter::().into_iter().collect() -} - fn build_router( root: fn() -> Result, platform_config: Result, diff --git a/packages/server/src/rt.rs b/packages/server/src/rt.rs index e6808f6081..5ed24ae49c 100644 --- a/packages/server/src/rt.rs +++ b/packages/server/src/rt.rs @@ -1,7 +1,4 @@ -use crate::{ - collect_raw_server_fns, render::SSRError, with_server_context, AxumServerFn, - DioxusServerContext, SSRState, ServeConfig, -}; +use crate::{render::SSRError, with_server_context, DioxusServerContext, SSRState, ServeConfig}; use crate::{ContextProviders, ProvideServerContext}; use axum::body; use axum::extract::State; @@ -13,7 +10,7 @@ use axum::{ }; use dioxus_lib::prelude::{Element, VirtualDom}; use http::header::*; -use server_fn::middleware::BoxedService; +use server_fn::{middleware::BoxedService, ServerFnTraitObj}; use std::sync::Arc; /// A extension trait with utilities for integrating Dioxus with your Axum router. @@ -194,10 +191,6 @@ where for f in collect_raw_server_fns() { self = register_server_fn_on_router(f, self, context_providers.clone()); } - // for (path, method) in server_fn::axum::server_fn_paths() { - // if let Some(service) = server_fn::axum::get_server_fn_service(&path, method.clone()) { - // } - // } self } @@ -215,7 +208,7 @@ where let path = f.path(); let method = f.method(); - tracing::info!("Registering server function: {} {}", method, path); + tracing::debug!("Registering server function: {} {}", method, path); let handler = move |req| handle_server_fns_inner(f, context_providers, req); match method { Method::GET => router.route(path, get(handler)), @@ -225,6 +218,12 @@ where } } +pub type AxumServerFn = ServerFnTraitObj, http::Response>; + +pub fn collect_raw_server_fns() -> Vec<&'static AxumServerFn> { + inventory::iter::().into_iter().collect() +} + /// A handler for Dioxus server functions. This will run the server function and return the result. async fn handle_server_fns_inner( f: &AxumServerFn, From d70928333ef9d168ea8a74e7e1ae003e84469bbd Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 23:44:01 -0700 Subject: [PATCH 127/301] clean up ws code --- Cargo.lock | 32 ++++++------------------------- Cargo.toml | 2 +- packages/cli/src/build/builder.rs | 5 ++++- packages/web/src/devtools.rs | 19 +++++------------- packages/web/src/lib.rs | 22 +++------------------ 5 files changed, 19 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34506edf33..1a4970c337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,12 +312,12 @@ dependencies = [ [[package]] name = "ansi-to-tui" -version = "6.0.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd01ee70df708c9ecb68705af08cee6fe85493992a7e205c52ddbe569314ba47" +checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" dependencies = [ "nom", - "ratatui 0.28.1", + "ratatui", "simdutf8", "smallvec", "thiserror 1.0.69", @@ -3737,7 +3737,7 @@ dependencies = [ "plist", "prettyplease", "proc-macro2", - "ratatui 0.29.0", + "ratatui", "rayon", "regex", "reqwest 0.12.15", @@ -11167,26 +11167,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "ratatui" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" -dependencies = [ - "bitflags 2.9.0", - "cassowary", - "compact_str", - "instability", - "itertools 0.13.0", - "lru 0.12.5", - "paste", - "strum 0.26.3", - "strum_macros 0.26.4", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.1.14", -] - [[package]] name = "ratatui" version = "0.29.0" @@ -13530,7 +13510,7 @@ dependencies = [ "color-eyre", "dioxus", "rand 0.8.5", - "ratatui 0.29.0", + "ratatui", "serde_json", "subsecond", "tokio", @@ -14557,7 +14537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d36b5738d666a2b4c91b7c24998a8588db724b3107258343ebf8824bf55b06d" dependencies = [ "rand 0.8.5", - "ratatui 0.29.0", + "ratatui", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3f040bb98a..dfb4e852f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -324,7 +324,7 @@ open = "5.1.2" gloo-dialogs = "0.2.0" # tui stuff -ansi-to-tui = "6.0" +ansi-to-tui = "7.0" ansi-to-html = "0.2.1" path-absolutize = "3.1" crossterm = { version = "0.29.0" } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 9dd1e48ccd..4b36b4232f 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -5,7 +5,10 @@ use crate::{ use anyhow::Context; use dioxus_cli_opt::process_file_to; use futures_util::future::OptionFuture; -use std::time::{Duration, Instant}; +use std::{ + env, + time::{Duration, Instant}, +}; use std::{ net::SocketAddr, path::{Path, PathBuf}, diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index 666b6abcca..df2a5bb61e 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -25,23 +25,19 @@ const POLL_INTERVAL_SCALE_FACTOR: i32 = 2; const TOAST_TIMEOUT: Duration = Duration::from_secs(5); const TOAST_TIMEOUT_LONG: Duration = Duration::from_secs(3600); // Duration::MAX is too long for JS. -pub(crate) fn init(runtime: Rc) -> UnboundedReceiver { +pub(crate) fn init() -> UnboundedReceiver { // Create the tx/rx pair that we'll use for the top-level future in the dioxus loop let (tx, rx) = unbounded(); // Wire up the websocket to the devserver - make_ws(runtime, tx.clone(), POLL_INTERVAL_MIN, false); + make_ws(tx.clone(), POLL_INTERVAL_MIN, false); + playground(tx); rx } -fn make_ws( - runtime: Rc, - tx: UnboundedSender, - poll_interval: i32, - reload: bool, -) { +fn make_ws(tx: UnboundedSender, poll_interval: i32, reload: bool) { // Get the location of the devserver, using the current location plus the /_dioxus path // The idea here being that the devserver is always located on the /_dioxus behind a proxy let location = web_sys::window().unwrap().location(); @@ -58,7 +54,6 @@ fn make_ws( // Set the onmessage handler to bounce messages off to the main dioxus loop let tx_ = tx.clone(); - let runtime_ = runtime.clone(); let ws_tx = ws.clone(); ws.set_onmessage(Some( Closure::::new(move |e: MessageEvent| { @@ -93,7 +88,7 @@ fn make_ws( // The devserver is telling us that it started a full rebuild. This does not mean that it is ready. Ok(DevserverMsg::HotPatchStart) => show_toast( "Hot-patching app...", - "Rust code changed and we are hot-patching.", + "Hot-patching modified Rust code.", ToastLevel::Info, TOAST_TIMEOUT_LONG, false, @@ -144,13 +139,11 @@ fn make_ws( // set timeout to reload the page in timeout_ms let tx = tx.clone(); - let runtime = runtime.clone(); web_sys::window() .unwrap() .set_timeout_with_callback_and_timeout_and_arguments_0( Closure::::new(move || { make_ws( - runtime.clone(), tx.clone(), POLL_INTERVAL_MAX.min(poll_interval * POLL_INTERVAL_SCALE_FACTOR), true, @@ -245,8 +238,6 @@ pub fn show_toast( false => "showDXToast", }; - // Create the guard before running eval which uses the global runtime context - js_sys::eval(&format!( r#" if (typeof {js_fn_name} !== "undefined") {{ diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index b82871f32b..11ed8911dd 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -2,23 +2,7 @@ #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] #![deny(missing_docs)] -//! Dioxus WebSys -//! -//! ## Overview -//! ------------ -//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for -//! Dioxus is one of the more advanced renderers, supporting: -//! - idle work -//! - animations -//! - jank-free rendering -//! - controlled components -//! - hydration -//! - and more. -//! -//! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate. -//! -//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide -//! validation of websys-specific features and not the general use of Dioxus. +//! # Dioxus Web use std::time::Duration; @@ -71,7 +55,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { let runtime = virtual_dom.runtime(); #[cfg(all(feature = "devtools", debug_assertions))] - let mut hotreload_rx = devtools::init(runtime.clone()); + let mut hotreload_rx = devtools::init(); let should_hydrate = web_config.hydrate; @@ -212,7 +196,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { "Hot-patch success!", &format!("App successfully patched in {} ms", hr_msg.ms_elapsed), devtools::ToastLevel::Success, - Duration::from_millis(500), + Duration::from_millis(1000), false, ); } From d95192ef0c235bb5b02a07a48969ed6d64d75539 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 23:53:42 -0700 Subject: [PATCH 128/301] bit more clean ups --- Cargo.lock | 2 -- examples/readme.rs | 20 -------------------- packages/cli/Cargo.toml | 7 +++---- packages/desktop/src/app.rs | 2 +- packages/desktop/src/ipc.rs | 2 +- packages/desktop/src/launch.rs | 2 +- packages/devtools/Cargo.toml | 2 +- packages/devtools/src/lib.rs | 2 ++ packages/fullstack-hooks/src/lib.rs | 1 - packages/web/Cargo.toml | 1 - packages/web/src/devtools.rs | 14 +++++--------- packages/web/src/document.rs | 1 + 12 files changed, 15 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a4970c337..c777fb9686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3727,7 +3727,6 @@ dependencies = [ "log", "manganis", "manganis-core", - "memmap", "memoize", "notify", "object 0.36.7", @@ -4582,7 +4581,6 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", - "subsecond", "tracing", "tracing-wasm", "wasm-bindgen", diff --git a/examples/readme.rs b/examples/readme.rs index a6be1c30cb..1c36c65c00 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -14,29 +14,9 @@ fn main() { fn app() -> Element { let mut count = use_signal(|| 0); - does_thing(); - match DOES_THING_META { - Entry::ServerFn { f } => f(), - Entry::Asset => {} - } - rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } } } - -#[link_section = concat!("__TEXT,__manganis")] -pub fn does_thing() { - println!("Hello from the dioxus example!"); -} - -#[link_section = concat!("__DATA,__manganis")] -pub static DOES_THING_META: Entry = Entry::ServerFn { f: does_thing }; - -#[repr(C, u8)] -enum Entry { - ServerFn { f: fn() }, - Asset, -} diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index a9f0903f88..b24cb747bc 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -111,8 +111,8 @@ manganis-core = { workspace = true } # Extracting data from an executable object = { workspace = true, features = ["all"] } -tokio-util = { version = "0.7.11", features = ["full"] } -itertools = "0.14.0" +tokio-util = { workspace = true, features = ["full"] } +itertools = { workspace = true } throbber-widgets-tui = "0.8.0" unicode-segmentation = "1.12.0" handlebars = "6.3.1" @@ -128,8 +128,7 @@ dircpy = "0.3.19" plist = "1.7.0" memoize = "0.5.1" -memmap = "0.7.0" -async-once-cell.workspace = true +async-once-cell = { workspace = true } depinfo = { workspace = true } [build-dependencies] diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 0320241bd3..fc4f992324 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -171,7 +171,7 @@ impl App { if let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() { let proxy = self.shared.proxy.clone(); dioxus_devtools::connect(endpoint, move |msg| { - _ = proxy.send_event(UserWindowEvent::DevServerEvent(msg)); + _ = proxy.send_event(UserWindowEvent::DevtoolsMsg(msg)); }) } } diff --git a/packages/desktop/src/ipc.rs b/packages/desktop/src/ipc.rs index b7ebd119c0..23d2e33f6b 100644 --- a/packages/desktop/src/ipc.rs +++ b/packages/desktop/src/ipc.rs @@ -28,7 +28,7 @@ pub enum UserWindowEvent { /// Handle a hotreload event, basically telling us to update our templates #[cfg(all(feature = "devtools", debug_assertions))] - DevServerEvent(dioxus_devtools::DevserverMsg), + DevtoolsMsg(dioxus_devtools::DevserverMsg), // Windows-only drag-n-drop fix events. WindowsDragDrop(WindowId), diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 81faa1ae0a..0107e2242e 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -55,7 +55,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config: UserWindowEvent::TrayIconEvent(evnt) => app.handle_tray_icon_event(evnt), #[cfg(all(feature = "devtools", debug_assertions))] - UserWindowEvent::DevServerEvent(msg) => app.handle_hot_reload_msg(msg), + UserWindowEvent::DevtoolsMsg(msg) => app.handle_hot_reload_msg(msg), // Windows-only drag-n-drop fix events. We need to call the interpreter drag-n-drop code. UserWindowEvent::WindowsDragDrop(id) => { diff --git a/packages/devtools/Cargo.toml b/packages/devtools/Cargo.toml index c425c0b235..1d2a9f67d2 100644 --- a/packages/devtools/Cargo.toml +++ b/packages/devtools/Cargo.toml @@ -15,9 +15,9 @@ dioxus-core = { workspace = true, features = ["serialize"] } dioxus-devtools-types = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +subsecond = { workspace = true } libloading = "0.8.3" libc = "0.2.155" -subsecond = { workspace = true } # hot reloading serve tracing = { workspace = true } diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index 45e18e5a2a..fa703a5de2 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -8,6 +8,8 @@ use std::{any::TypeId, cell::Cell, ffi::CString, path::PathBuf, rc::Rc}; use subsecond::JumpTable; use warnings::Warning; +pub use subsecond; + pub struct Devtools { main_fn: Cell Element>, } diff --git a/packages/fullstack-hooks/src/lib.rs b/packages/fullstack-hooks/src/lib.rs index e151693baf..e83a83ebf0 100644 --- a/packages/fullstack-hooks/src/lib.rs +++ b/packages/fullstack-hooks/src/lib.rs @@ -3,6 +3,5 @@ mod hooks; pub use hooks::*; - mod streaming; pub use streaming::*; diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 88f0dd5d47..7963d38c5f 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -36,7 +36,6 @@ serde_json = { version = "1.0", optional = true } serde = { version = "1.0", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } -subsecond = { workspace = true } ciborium = { workspace = true, optional = true } async-trait = { workspace = true, optional = true } diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index df2a5bb61e..d304dfe738 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -4,17 +4,13 @@ //! We also set up a little recursive timer that will attempt to reconnect if the connection is lost. use std::fmt::Display; -use std::rc::Rc; use std::time::Duration; -use dioxus_core::prelude::RuntimeGuard; -use dioxus_core::{Runtime, ScopeId}; use dioxus_devtools::{ClientMsg, DevserverMsg, HotReloadMsg}; -use dioxus_document::eval; use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use js_sys::JsString; +use wasm_bindgen::JsCast; use wasm_bindgen::{closure::Closure, JsValue}; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast}; use web_sys::{window, CloseEvent, MessageEvent, WebSocket}; const POLL_INTERVAL_MIN: i32 = 250; @@ -170,7 +166,7 @@ fn make_ws(tx: UnboundedSender, poll_interval: i32, reload: bool) ws_tx.send_with_str( &serde_json::to_string(&ClientMsg::Initialize { build_id: dioxus_devtools::build_id(), - aslr_reference: subsecond::aslr_reference() as _, + aslr_reference: dioxus_devtools::subsecond::aslr_reference() as _, }) .unwrap(), ); @@ -194,7 +190,7 @@ fn make_ws(tx: UnboundedSender, poll_interval: i32, reload: bool) } /// Represents what color the toast should have. -pub enum ToastLevel { +pub(crate) enum ToastLevel { /// Green Success, /// Blue @@ -213,7 +209,7 @@ impl Display for ToastLevel { } } -pub fn close_toast() { +pub(crate) fn close_toast() { js_sys::eval( r#" if (typeof closeDXToast !== "undefined") { @@ -224,7 +220,7 @@ pub fn close_toast() { } /// Displays a toast to the developer. -pub fn show_toast( +pub(crate) fn show_toast( header_text: &str, message: &str, level: ToastLevel, diff --git a/packages/web/src/document.rs b/packages/web/src/document.rs index 277dcc2b87..9ecf4ca97a 100644 --- a/packages/web/src/document.rs +++ b/packages/web/src/document.rs @@ -31,6 +31,7 @@ impl JSOwner { } } } + #[wasm_bindgen::prelude::wasm_bindgen(module = "/src/js/eval.js")] extern "C" { pub type WeakDioxusChannel; From 5cb22cbedbaa40ad8434113d768cf7208a35fb5b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Apr 2025 23:57:16 -0700 Subject: [PATCH 129/301] condense --- packages/subsecond/subsecond-types/src/lib.rs | 54 +++++++++++++++++-- .../subsecond/subsecond-types/src/nohasher.rs | 51 ------------------ 2 files changed, 50 insertions(+), 55 deletions(-) delete mode 100644 packages/subsecond/subsecond-types/src/nohasher.rs diff --git a/packages/subsecond/subsecond-types/src/lib.rs b/packages/subsecond/subsecond-types/src/lib.rs index addb71307d..5c6e7f8925 100644 --- a/packages/subsecond/subsecond-types/src/lib.rs +++ b/packages/subsecond/subsecond-types/src/lib.rs @@ -1,8 +1,9 @@ use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf}; - -mod nohasher; -pub use nohasher::AddressMap; +use std::{ + collections::HashMap, + hash::{BuildHasherDefault, Hasher}, + path::PathBuf, +}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct JumpTable { @@ -44,3 +45,48 @@ pub struct JumpTable { /// for the ifuncs in the ifunc table pub ifunc_count: u64, } + +/// An address to address hashmap that does not hash addresses since addresses are by definition unique. +pub type AddressMap = HashMap; +pub type BuildAddressHasher = BuildHasherDefault; + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct AddressHasher(u64); +impl Hasher for AddressHasher { + fn write(&mut self, _: &[u8]) { + panic!("Invalid use of NoHashHasher") + } + fn write_u8(&mut self, n: u8) { + self.0 = u64::from(n) + } + fn write_u16(&mut self, n: u16) { + self.0 = u64::from(n) + } + fn write_u32(&mut self, n: u32) { + self.0 = u64::from(n) + } + fn write_u64(&mut self, n: u64) { + self.0 = n + } + fn write_usize(&mut self, n: usize) { + self.0 = n as u64 + } + fn write_i8(&mut self, n: i8) { + self.0 = n as u64 + } + fn write_i16(&mut self, n: i16) { + self.0 = n as u64 + } + fn write_i32(&mut self, n: i32) { + self.0 = n as u64 + } + fn write_i64(&mut self, n: i64) { + self.0 = n as u64 + } + fn write_isize(&mut self, n: isize) { + self.0 = n as u64 + } + fn finish(&self) -> u64 { + self.0 + } +} diff --git a/packages/subsecond/subsecond-types/src/nohasher.rs b/packages/subsecond/subsecond-types/src/nohasher.rs deleted file mode 100644 index 6547af2c13..0000000000 --- a/packages/subsecond/subsecond-types/src/nohasher.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::{ - collections::HashMap, - hash::{BuildHasherDefault, Hasher}, -}; - -/// An address to address hashmap that does not hash addresses since addresses are by definition unique. -pub type AddressMap = HashMap; - -pub type BuildAddressHasher = BuildHasherDefault; - -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct AddressHasher(u64); - -impl Hasher for AddressHasher { - fn write(&mut self, _: &[u8]) { - panic!("Invalid use of NoHashHasher") - } - fn write_u8(&mut self, n: u8) { - self.0 = u64::from(n) - } - fn write_u16(&mut self, n: u16) { - self.0 = u64::from(n) - } - fn write_u32(&mut self, n: u32) { - self.0 = u64::from(n) - } - fn write_u64(&mut self, n: u64) { - self.0 = n - } - fn write_usize(&mut self, n: usize) { - self.0 = n as u64 - } - fn write_i8(&mut self, n: i8) { - self.0 = n as u64 - } - fn write_i16(&mut self, n: i16) { - self.0 = n as u64 - } - fn write_i32(&mut self, n: i32) { - self.0 = n as u64 - } - fn write_i64(&mut self, n: i64) { - self.0 = n as u64 - } - fn write_isize(&mut self, n: isize) { - self.0 = n as u64 - } - fn finish(&self) -> u64 { - self.0 - } -} From 9ef6af4800e659de327163de237fff2f1a932e14 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 00:00:27 -0700 Subject: [PATCH 130/301] undo file moves --- .../fullstack-auth/.gitignore | 0 .../fullstack-auth/Cargo.toml | 0 .../fullstack-auth/src/auth.rs | 0 .../fullstack-auth/src/main.rs | 0 .../fullstack-desktop/.gitignore | 0 .../fullstack-desktop/Cargo.toml | 0 .../fullstack-desktop/src/main.rs | 0 .../fullstack-hackernews/.gitignore | 0 .../fullstack-hackernews/Cargo.toml | 0 .../fullstack-hackernews/assets/hackernews.css | 0 .../fullstack-hackernews/src/main.rs | 0 .../fullstack-hello-world/.gitignore | 0 .../fullstack-hello-world/Cargo.toml | 0 .../fullstack-hello-world/assets/hello.css | 0 .../fullstack-hello-world/src/main.rs | 0 .../fullstack-router/.gitignore | 0 .../fullstack-router/Cargo.toml | 0 .../fullstack-router/src/main.rs | 0 .../fullstack-streaming/.gitignore | 0 .../fullstack-streaming/Cargo.toml | 0 .../fullstack-streaming/src/main.rs | 0 {example-projects => examples}/pwa/Cargo.toml | 0 {example-projects => examples}/pwa/Dioxus.toml | 0 {example-projects => examples}/pwa/LICENSE | 0 {example-projects => examples}/pwa/README.md | 0 {example-projects => examples}/pwa/index.html | 0 .../pwa/public/favicon.ico | Bin .../pwa/public/logo_192.png | Bin .../pwa/public/logo_512.png | Bin .../pwa/public/manifest.json | 0 {example-projects => examples}/pwa/public/sw.js | 0 {example-projects => examples}/pwa/src/main.rs | 0 .../playwright-tests/nested-suspense/Cargo.toml | 2 +- 33 files changed, 1 insertion(+), 1 deletion(-) rename {example-projects => examples}/fullstack-auth/.gitignore (100%) rename {example-projects => examples}/fullstack-auth/Cargo.toml (100%) rename {example-projects => examples}/fullstack-auth/src/auth.rs (100%) rename {example-projects => examples}/fullstack-auth/src/main.rs (100%) rename {example-projects => examples}/fullstack-desktop/.gitignore (100%) rename {example-projects => examples}/fullstack-desktop/Cargo.toml (100%) rename {example-projects => examples}/fullstack-desktop/src/main.rs (100%) rename {example-projects => examples}/fullstack-hackernews/.gitignore (100%) rename {example-projects => examples}/fullstack-hackernews/Cargo.toml (100%) rename {example-projects => examples}/fullstack-hackernews/assets/hackernews.css (100%) rename {example-projects => examples}/fullstack-hackernews/src/main.rs (100%) rename {example-projects => examples}/fullstack-hello-world/.gitignore (100%) rename {example-projects => examples}/fullstack-hello-world/Cargo.toml (100%) rename {example-projects => examples}/fullstack-hello-world/assets/hello.css (100%) rename {example-projects => examples}/fullstack-hello-world/src/main.rs (100%) rename {example-projects => examples}/fullstack-router/.gitignore (100%) rename {example-projects => examples}/fullstack-router/Cargo.toml (100%) rename {example-projects => examples}/fullstack-router/src/main.rs (100%) rename {example-projects => examples}/fullstack-streaming/.gitignore (100%) rename {example-projects => examples}/fullstack-streaming/Cargo.toml (100%) rename {example-projects => examples}/fullstack-streaming/src/main.rs (100%) rename {example-projects => examples}/pwa/Cargo.toml (100%) rename {example-projects => examples}/pwa/Dioxus.toml (100%) rename {example-projects => examples}/pwa/LICENSE (100%) rename {example-projects => examples}/pwa/README.md (100%) rename {example-projects => examples}/pwa/index.html (100%) rename {example-projects => examples}/pwa/public/favicon.ico (100%) rename {example-projects => examples}/pwa/public/logo_192.png (100%) rename {example-projects => examples}/pwa/public/logo_512.png (100%) rename {example-projects => examples}/pwa/public/manifest.json (100%) rename {example-projects => examples}/pwa/public/sw.js (100%) rename {example-projects => examples}/pwa/src/main.rs (100%) diff --git a/example-projects/fullstack-auth/.gitignore b/examples/fullstack-auth/.gitignore similarity index 100% rename from example-projects/fullstack-auth/.gitignore rename to examples/fullstack-auth/.gitignore diff --git a/example-projects/fullstack-auth/Cargo.toml b/examples/fullstack-auth/Cargo.toml similarity index 100% rename from example-projects/fullstack-auth/Cargo.toml rename to examples/fullstack-auth/Cargo.toml diff --git a/example-projects/fullstack-auth/src/auth.rs b/examples/fullstack-auth/src/auth.rs similarity index 100% rename from example-projects/fullstack-auth/src/auth.rs rename to examples/fullstack-auth/src/auth.rs diff --git a/example-projects/fullstack-auth/src/main.rs b/examples/fullstack-auth/src/main.rs similarity index 100% rename from example-projects/fullstack-auth/src/main.rs rename to examples/fullstack-auth/src/main.rs diff --git a/example-projects/fullstack-desktop/.gitignore b/examples/fullstack-desktop/.gitignore similarity index 100% rename from example-projects/fullstack-desktop/.gitignore rename to examples/fullstack-desktop/.gitignore diff --git a/example-projects/fullstack-desktop/Cargo.toml b/examples/fullstack-desktop/Cargo.toml similarity index 100% rename from example-projects/fullstack-desktop/Cargo.toml rename to examples/fullstack-desktop/Cargo.toml diff --git a/example-projects/fullstack-desktop/src/main.rs b/examples/fullstack-desktop/src/main.rs similarity index 100% rename from example-projects/fullstack-desktop/src/main.rs rename to examples/fullstack-desktop/src/main.rs diff --git a/example-projects/fullstack-hackernews/.gitignore b/examples/fullstack-hackernews/.gitignore similarity index 100% rename from example-projects/fullstack-hackernews/.gitignore rename to examples/fullstack-hackernews/.gitignore diff --git a/example-projects/fullstack-hackernews/Cargo.toml b/examples/fullstack-hackernews/Cargo.toml similarity index 100% rename from example-projects/fullstack-hackernews/Cargo.toml rename to examples/fullstack-hackernews/Cargo.toml diff --git a/example-projects/fullstack-hackernews/assets/hackernews.css b/examples/fullstack-hackernews/assets/hackernews.css similarity index 100% rename from example-projects/fullstack-hackernews/assets/hackernews.css rename to examples/fullstack-hackernews/assets/hackernews.css diff --git a/example-projects/fullstack-hackernews/src/main.rs b/examples/fullstack-hackernews/src/main.rs similarity index 100% rename from example-projects/fullstack-hackernews/src/main.rs rename to examples/fullstack-hackernews/src/main.rs diff --git a/example-projects/fullstack-hello-world/.gitignore b/examples/fullstack-hello-world/.gitignore similarity index 100% rename from example-projects/fullstack-hello-world/.gitignore rename to examples/fullstack-hello-world/.gitignore diff --git a/example-projects/fullstack-hello-world/Cargo.toml b/examples/fullstack-hello-world/Cargo.toml similarity index 100% rename from example-projects/fullstack-hello-world/Cargo.toml rename to examples/fullstack-hello-world/Cargo.toml diff --git a/example-projects/fullstack-hello-world/assets/hello.css b/examples/fullstack-hello-world/assets/hello.css similarity index 100% rename from example-projects/fullstack-hello-world/assets/hello.css rename to examples/fullstack-hello-world/assets/hello.css diff --git a/example-projects/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs similarity index 100% rename from example-projects/fullstack-hello-world/src/main.rs rename to examples/fullstack-hello-world/src/main.rs diff --git a/example-projects/fullstack-router/.gitignore b/examples/fullstack-router/.gitignore similarity index 100% rename from example-projects/fullstack-router/.gitignore rename to examples/fullstack-router/.gitignore diff --git a/example-projects/fullstack-router/Cargo.toml b/examples/fullstack-router/Cargo.toml similarity index 100% rename from example-projects/fullstack-router/Cargo.toml rename to examples/fullstack-router/Cargo.toml diff --git a/example-projects/fullstack-router/src/main.rs b/examples/fullstack-router/src/main.rs similarity index 100% rename from example-projects/fullstack-router/src/main.rs rename to examples/fullstack-router/src/main.rs diff --git a/example-projects/fullstack-streaming/.gitignore b/examples/fullstack-streaming/.gitignore similarity index 100% rename from example-projects/fullstack-streaming/.gitignore rename to examples/fullstack-streaming/.gitignore diff --git a/example-projects/fullstack-streaming/Cargo.toml b/examples/fullstack-streaming/Cargo.toml similarity index 100% rename from example-projects/fullstack-streaming/Cargo.toml rename to examples/fullstack-streaming/Cargo.toml diff --git a/example-projects/fullstack-streaming/src/main.rs b/examples/fullstack-streaming/src/main.rs similarity index 100% rename from example-projects/fullstack-streaming/src/main.rs rename to examples/fullstack-streaming/src/main.rs diff --git a/example-projects/pwa/Cargo.toml b/examples/pwa/Cargo.toml similarity index 100% rename from example-projects/pwa/Cargo.toml rename to examples/pwa/Cargo.toml diff --git a/example-projects/pwa/Dioxus.toml b/examples/pwa/Dioxus.toml similarity index 100% rename from example-projects/pwa/Dioxus.toml rename to examples/pwa/Dioxus.toml diff --git a/example-projects/pwa/LICENSE b/examples/pwa/LICENSE similarity index 100% rename from example-projects/pwa/LICENSE rename to examples/pwa/LICENSE diff --git a/example-projects/pwa/README.md b/examples/pwa/README.md similarity index 100% rename from example-projects/pwa/README.md rename to examples/pwa/README.md diff --git a/example-projects/pwa/index.html b/examples/pwa/index.html similarity index 100% rename from example-projects/pwa/index.html rename to examples/pwa/index.html diff --git a/example-projects/pwa/public/favicon.ico b/examples/pwa/public/favicon.ico similarity index 100% rename from example-projects/pwa/public/favicon.ico rename to examples/pwa/public/favicon.ico diff --git a/example-projects/pwa/public/logo_192.png b/examples/pwa/public/logo_192.png similarity index 100% rename from example-projects/pwa/public/logo_192.png rename to examples/pwa/public/logo_192.png diff --git a/example-projects/pwa/public/logo_512.png b/examples/pwa/public/logo_512.png similarity index 100% rename from example-projects/pwa/public/logo_512.png rename to examples/pwa/public/logo_512.png diff --git a/example-projects/pwa/public/manifest.json b/examples/pwa/public/manifest.json similarity index 100% rename from example-projects/pwa/public/manifest.json rename to examples/pwa/public/manifest.json diff --git a/example-projects/pwa/public/sw.js b/examples/pwa/public/sw.js similarity index 100% rename from example-projects/pwa/public/sw.js rename to examples/pwa/public/sw.js diff --git a/example-projects/pwa/src/main.rs b/examples/pwa/src/main.rs similarity index 100% rename from example-projects/pwa/src/main.rs rename to examples/pwa/src/main.rs diff --git a/packages/playwright-tests/nested-suspense/Cargo.toml b/packages/playwright-tests/nested-suspense/Cargo.toml index 67f6ca83de..70fc6be813 100644 --- a/packages/playwright-tests/nested-suspense/Cargo.toml +++ b/packages/playwright-tests/nested-suspense/Cargo.toml @@ -13,7 +13,7 @@ tokio = { workspace = true, features = ["full"], optional = true } [features] default = [] -server = ["dioxus/server", "dep:tokio"] +server = ["dioxus/server", "tokio"] web = ["dioxus/web"] # We need a separate bin for the SSG build to avoid conflicting server caches From 6b73f233a2a6c26825fcc58dc2e9d029e3f34686 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 00:01:05 -0700 Subject: [PATCH 131/301] move back other project --- {example-projects => examples}/tailwind/.gitignore | 0 {example-projects => examples}/tailwind/Cargo.toml | 0 {example-projects => examples}/tailwind/Dioxus.toml | 0 {example-projects => examples}/tailwind/README.md | 0 {example-projects => examples}/tailwind/input.css | 0 {example-projects => examples}/tailwind/public/tailwind.css | 0 {example-projects => examples}/tailwind/src/main.rs | 0 {example-projects => examples}/tailwind/tailwind.config.js | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {example-projects => examples}/tailwind/.gitignore (100%) rename {example-projects => examples}/tailwind/Cargo.toml (100%) rename {example-projects => examples}/tailwind/Dioxus.toml (100%) rename {example-projects => examples}/tailwind/README.md (100%) rename {example-projects => examples}/tailwind/input.css (100%) rename {example-projects => examples}/tailwind/public/tailwind.css (100%) rename {example-projects => examples}/tailwind/src/main.rs (100%) rename {example-projects => examples}/tailwind/tailwind.config.js (100%) diff --git a/example-projects/tailwind/.gitignore b/examples/tailwind/.gitignore similarity index 100% rename from example-projects/tailwind/.gitignore rename to examples/tailwind/.gitignore diff --git a/example-projects/tailwind/Cargo.toml b/examples/tailwind/Cargo.toml similarity index 100% rename from example-projects/tailwind/Cargo.toml rename to examples/tailwind/Cargo.toml diff --git a/example-projects/tailwind/Dioxus.toml b/examples/tailwind/Dioxus.toml similarity index 100% rename from example-projects/tailwind/Dioxus.toml rename to examples/tailwind/Dioxus.toml diff --git a/example-projects/tailwind/README.md b/examples/tailwind/README.md similarity index 100% rename from example-projects/tailwind/README.md rename to examples/tailwind/README.md diff --git a/example-projects/tailwind/input.css b/examples/tailwind/input.css similarity index 100% rename from example-projects/tailwind/input.css rename to examples/tailwind/input.css diff --git a/example-projects/tailwind/public/tailwind.css b/examples/tailwind/public/tailwind.css similarity index 100% rename from example-projects/tailwind/public/tailwind.css rename to examples/tailwind/public/tailwind.css diff --git a/example-projects/tailwind/src/main.rs b/examples/tailwind/src/main.rs similarity index 100% rename from example-projects/tailwind/src/main.rs rename to examples/tailwind/src/main.rs diff --git a/example-projects/tailwind/tailwind.config.js b/examples/tailwind/tailwind.config.js similarity index 100% rename from example-projects/tailwind/tailwind.config.js rename to examples/tailwind/tailwind.config.js From 2e1ec20e93f7504bc7aa6b115f0857f1eead81b2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 00:01:42 -0700 Subject: [PATCH 132/301] move other project --- {examples => example-projects}/fullstack-hackernews/.gitignore | 0 {examples => example-projects}/fullstack-hackernews/Cargo.toml | 0 .../fullstack-hackernews/assets/hackernews.css | 0 {examples => example-projects}/fullstack-hackernews/src/main.rs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {examples => example-projects}/fullstack-hackernews/.gitignore (100%) rename {examples => example-projects}/fullstack-hackernews/Cargo.toml (100%) rename {examples => example-projects}/fullstack-hackernews/assets/hackernews.css (100%) rename {examples => example-projects}/fullstack-hackernews/src/main.rs (100%) diff --git a/examples/fullstack-hackernews/.gitignore b/example-projects/fullstack-hackernews/.gitignore similarity index 100% rename from examples/fullstack-hackernews/.gitignore rename to example-projects/fullstack-hackernews/.gitignore diff --git a/examples/fullstack-hackernews/Cargo.toml b/example-projects/fullstack-hackernews/Cargo.toml similarity index 100% rename from examples/fullstack-hackernews/Cargo.toml rename to example-projects/fullstack-hackernews/Cargo.toml diff --git a/examples/fullstack-hackernews/assets/hackernews.css b/example-projects/fullstack-hackernews/assets/hackernews.css similarity index 100% rename from examples/fullstack-hackernews/assets/hackernews.css rename to example-projects/fullstack-hackernews/assets/hackernews.css diff --git a/examples/fullstack-hackernews/src/main.rs b/example-projects/fullstack-hackernews/src/main.rs similarity index 100% rename from examples/fullstack-hackernews/src/main.rs rename to example-projects/fullstack-hackernews/src/main.rs From 66e7393883d7b8cd30eacdffb652c3587a153aaa Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 00:07:51 -0700 Subject: [PATCH 133/301] migrate out harness and janky CLI --- Cargo.toml | 22 +- packages/subsecond/data/.gitignore | 2 - packages/subsecond/data/README.md | 1 - .../subsecond-cli-support/Cargo.toml | 2 +- packages/subsecond/subsecond-cli/Cargo.toml | 45 -- .../subsecond/subsecond-cli/src/index.html | 48 -- packages/subsecond/subsecond-cli/src/main.rs | 665 ------------------ .../subsecond/subsecond-harness/Cargo.toml | 26 - .../subsecond/subsecond-harness/README.md | 0 .../subsecond-harness/assets/test.css | 12 - .../subsecond-harness/src/dioxus_demo.rs | 177 ----- .../subsecond-harness/src/loop_demo.rs | 10 - .../subsecond/subsecond-harness/src/main.rs | 23 - .../subsecond-harness/src/tui_demo.rs | 113 --- .../subsecond-harness/src/ws_conn.rs | 35 - packages/subsecond/subsecond-macro/Cargo.toml | 15 - packages/subsecond/subsecond-macro/src/lib.rs | 167 ----- 17 files changed, 11 insertions(+), 1352 deletions(-) delete mode 100644 packages/subsecond/data/.gitignore delete mode 100644 packages/subsecond/data/README.md delete mode 100644 packages/subsecond/subsecond-cli/Cargo.toml delete mode 100644 packages/subsecond/subsecond-cli/src/index.html delete mode 100644 packages/subsecond/subsecond-cli/src/main.rs delete mode 100644 packages/subsecond/subsecond-harness/Cargo.toml delete mode 100644 packages/subsecond/subsecond-harness/README.md delete mode 100644 packages/subsecond/subsecond-harness/assets/test.css delete mode 100644 packages/subsecond/subsecond-harness/src/dioxus_demo.rs delete mode 100644 packages/subsecond/subsecond-harness/src/loop_demo.rs delete mode 100644 packages/subsecond/subsecond-harness/src/main.rs delete mode 100644 packages/subsecond/subsecond-harness/src/tui_demo.rs delete mode 100644 packages/subsecond/subsecond-harness/src/ws_conn.rs delete mode 100644 packages/subsecond/subsecond-macro/Cargo.toml delete mode 100644 packages/subsecond/subsecond-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index dfb4e852f8..f3be320c56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,9 +68,9 @@ members = [ "packages/dx-wire-format", "packages/logger", "packages/config-macros", - "packages/depinfo", "packages/native", "packages/asset-resolver", + "packages/depinfo", "packages/server", # Playwright tests @@ -93,11 +93,8 @@ members = [ # subsecond "packages/subsecond/subsecond", - "packages/subsecond/subsecond-macro", "packages/subsecond/subsecond-cli-support", "packages/subsecond/subsecond-types", - "packages/subsecond/subsecond-cli", - "packages/subsecond/subsecond-harness", # Full project examples "example-projects/fullstack-hackernews", @@ -106,13 +103,13 @@ members = [ "example-projects/file-explorer", # Simple examples that require a crate - "example-projects/tailwind", - "example-projects/pwa", - "example-projects/fullstack-hello-world", - "example-projects/fullstack-router", - "example-projects/fullstack-streaming", - "example-projects/fullstack-desktop", - "example-projects/fullstack-auth", + "examples/tailwind", + "examples/pwa", + "examples/fullstack-hello-world", + "examples/fullstack-router", + "examples/fullstack-streaming", + "examples/fullstack-desktop", + "examples/fullstack-auth", # Playwright tests "packages/playwright-tests/liveview", @@ -294,10 +291,10 @@ wasmparser = "0.226.0" itertools = "0.14.0" object = { version = "0.36.0" } bincode = "1.3.3" -url = "2.3.1" inventory = { version = "0.3.5" } macro-string = "0.1.4" walkdir = "2.5.0" +url = "2.3.1" separator = "0.4.1" pretty_assertions = "1.4.0" serde_repr = "0.1" @@ -331,6 +328,7 @@ crossterm = { version = "0.29.0" } ratatui = { version = "0.29.0" } shell-words = "1.1.0" color-eyre = "0.6.3" + # native keyboard-types = { version = "0.7", default-features = false } winit = { version = "0.30.2", features = ["rwh_06"] } diff --git a/packages/subsecond/data/.gitignore b/packages/subsecond/data/.gitignore deleted file mode 100644 index d6b7ef32c8..0000000000 --- a/packages/subsecond/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/packages/subsecond/data/README.md b/packages/subsecond/data/README.md deleted file mode 100644 index c122cd9ce7..0000000000 --- a/packages/subsecond/data/README.md +++ /dev/null @@ -1 +0,0 @@ -additional data for the subsecond harness to ensure everything is working diff --git a/packages/subsecond/subsecond-cli-support/Cargo.toml b/packages/subsecond/subsecond-cli-support/Cargo.toml index 0ae05b5686..59dcb21b5c 100644 --- a/packages/subsecond/subsecond-cli-support/Cargo.toml +++ b/packages/subsecond/subsecond-cli-support/Cargo.toml @@ -7,7 +7,7 @@ version.workspace = true object = { workspace = true, features = ["all"] } pretty-hex = "0.4.1" pretty_assertions = "1.4.1" -bincode = "1.3.3" +bincode = { workspace = true } tokio = { workspace = true, features = ["full"] } memmap = "0.7.0" rustc-demangle = "0.1.24" diff --git a/packages/subsecond/subsecond-cli/Cargo.toml b/packages/subsecond/subsecond-cli/Cargo.toml deleted file mode 100644 index 6062c6f704..0000000000 --- a/packages/subsecond/subsecond-cli/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "subsecond-cli" -edition = "2021" -version.workspace = true - -[dependencies] -anyhow = "1.0.95" -cargo_metadata = "0.19.1" -# krates = "0.17.5" -clap = { version = "4.5.28", features = ["derive"] } -futures = "0.3.31" -futures-channel = "0.3.31" -futures-util = "0.3.31" -notify = "8.0.0" -serde = { version = "1.0.217", features = ["derive"] } -serde_json = "1.0.138" -shell-words = "1.1.0" -tokio = { version = "1.43.0", features = ["full"] } -tracing = "0.1.41" -urlencoding = "2.1.3" -gimli = "0.31.1" -itertools = "0.14.0" -object = { workspace = true, features = ["all"] } -pretty-hex = "0.4.1" -pretty_assertions = "1.4.1" -bincode = "1.3.3" -include_dir = "0.7.3" -libc = "0.2.155" -libloading = "0.8.3" -macext = "0.2.1" -memmap = "0.7.0" -rustc-demangle = "0.1.24" -sysinfo = "0.33.1" -page_size = "0.6.0" -ouroboros = "0.18.5" -walkdir = "2.5.0" -tokio-tungstenite = "0.23.0" -crossterm = { workspace = true } -subsecond-cli-support = { path = "../subsecond-cli-support" } -target-lexicon = "0.13.2" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } -axum = { workspace = true, features = ["ws"] } -axum-server = { workspace = true, features = ["tls-rustls"] } -axum-extra = { workspace = true, features = ["typed-header"] } -tower-http = { workspace = true, features = ["full"] } diff --git a/packages/subsecond/subsecond-cli/src/index.html b/packages/subsecond/subsecond-cli/src/index.html deleted file mode 100644 index f8ff041c01..0000000000 --- a/packages/subsecond/subsecond-cli/src/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - Subsecond - - - - -
- - diff --git a/packages/subsecond/subsecond-cli/src/main.rs b/packages/subsecond/subsecond-cli/src/main.rs deleted file mode 100644 index a62f368452..0000000000 --- a/packages/subsecond/subsecond-cli/src/main.rs +++ /dev/null @@ -1,665 +0,0 @@ -use anyhow::Context; -use cargo_metadata::camino::Utf8PathBuf; -use clap::Parser; -use futures::{SinkExt, StreamExt}; -use itertools::Itertools; -use notify::{ - event::{DataChange, ModifyKind}, - Watcher, -}; -use object::{write::Object, Architecture}; -use serde::Deserialize; -use std::{collections::HashMap, env, ffi::OsStr, path::PathBuf, process::Stdio, time::SystemTime}; -use subsecond_cli_support::{create_jump_table, satisfy_got_imports}; -use target_lexicon::{Environment, Triple}; -use tokio::{ - io::AsyncBufReadExt, - net::TcpListener, - process::{Child, Command}, - time::Instant, -}; -use tokio_tungstenite::WebSocketStream; -use tracing::info; - -#[derive(Debug, Parser)] -struct Args { - #[clap(long)] - target: Option, -} - -/// The main loop of the hotreload process -/// -/// 1. Create initial "fat" build -/// 2. Identify hotpoints from the incrementals. We ignore dependency hotpoints for now, but eventually might want to aggregate workspace deps together. -/// 3. Wait for changes to the main.rs file -/// 4. Perform a "fast" build -/// 5. Diff the object files, walking relocations, preserving local statics -/// 6. Create a minimal patch file to load into the process, including the changed symbol list -/// 7. Pause the process with lldb, run the "hotfn_load_binary_patch" command and then continue -/// 8. Repeat -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Go through the linker if we need to - if let Ok(action) = std::env::var("HOTRELOAD_LINK") { - return link(action).await; - } - - tracing_subscriber::fmt::init(); - - let args = Args::parse(); - let target: Triple = args - .target - .map(|t| t.parse().unwrap()) - .unwrap_or_else(|| Triple::host()); - - // Save the state of the rust files - let src_folder = subsecond_folder().join("subsecond-harness/src/"); - let main_rs = src_folder.join("main.rs"); - - // Modify the main.rs mtime so we skip "fresh" builds - // Basically `touch main.rs` in the directory - std::fs::File::open(&main_rs)?.set_modified(SystemTime::now())?; - - // Perform the initial build - let epoch = SystemTime::UNIX_EPOCH; - let now = std::time::Instant::now(); - tracing::debug!("Starting build for target {target:?}..."); - let result = initial_build(&target).await?; - tracing::debug!( - "Initial build: {:?} -> {}", - now.elapsed(), - &result.output_location, - ); - - // copy the exe and give it a "fat" name. todo: wipe the ld entry that points to `/deps` - let exe = &result.output_location; - let fat_exe = exe.with_file_name(format!( - "fatharness-{}", - epoch.elapsed().unwrap().as_millis() - )); - std::fs::copy(&exe, &fat_exe).unwrap(); - - // Launch the fat exe. We'll overwrite the slim exe location, so this prevents the app from bugging out - let app = launch_app(&fat_exe, &target)?; - - // Wait for the websocket to come up - let mut client = wait_for_ws(9393).await?.unwrap(); - tracing::info!("Client connected"); - - // Watch the source folder for changes - let mut watcher = FsWatcher::watch(src_folder)?; - - while let Some(Ok(event)) = watcher.rx.next().await { - if !watcher.file_changed(event.paths.first().unwrap()) { - continue; - } - - tracing::info!("Fast reloading... "); - - let started = Instant::now(); - let out = match fast_build(&result, &target, client.aslr_reference).await { - Ok(output_temp) => output_temp, - Err(e) => { - tracing::warn!("Fast build failed: {e}"); - continue; - } - }; - - // Assemble the jump table of redirected addresses - // todo: keep track of this and merge it over time - let jump_table = - create_jump_table(fat_exe.as_std_path(), out.as_std_path(), &target).unwrap(); - - // Rebase the wasm binary to be relocatable once the jump table is generated - if target.architecture == target_lexicon::Architecture::Wasm32 { - let old_bytes = std::fs::read(&fat_exe).unwrap(); - let new_bytes = std::fs::read(&out).unwrap(); - let res_ = satisfy_got_imports(&old_bytes, &new_bytes).unwrap(); - std::fs::write(&out, res_).unwrap(); - } - - client - .socket - .send(tokio_tungstenite::tungstenite::Message::Text( - serde_json::to_string(&jump_table).unwrap(), - )) - .await?; - - if target.architecture == target_lexicon::Architecture::Wasm32 { - let _ = std::fs::copy( - out.as_std_path(), - static_folder().join(out.file_name().unwrap()), - ); - } - - tracing::info!("Patching complete in {}ms", started.elapsed().as_millis()) - } - - drop(app); - - Ok(()) -} - -fn launch_app(fat_exe: &Utf8PathBuf, target: &Triple) -> Result { - let app = match target.architecture { - target_lexicon::Architecture::Wasm32 => { - info!("Serving wasm at http://127.0.0.1:9393"); - Command::new("python3") - .current_dir(static_folder()) - .arg("-m") - .arg("http.server") - .arg("9394") - .arg("--directory") - .arg(".") - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()? - } - _ => Command::new(fat_exe).kill_on_drop(true).spawn()?, - }; - - Ok(app) -} -async fn initial_build(target: &Triple) -> anyhow::Result { - // Perform the initial build and print out the link arguments. Don't strip dead code and preserve temp files. - // This results in a "fat" executable that we can bind to - // - // todo: clean up the temps manually - let mut build = Command::new("cargo"); - - build - .arg("rustc") - .arg("--package") - .arg("subsecond-harness") - .arg("--bin") - .arg("subsecond-harness") - .arg("--profile") - .arg("subsecond-dev") - .arg("--message-format") - .arg("json-diagnostic-rendered-ansi") - .arg("--verbose") - .arg("--target") - .arg(target.to_string()); - - match target.architecture { - target_lexicon::Architecture::Wasm32 => { - build.arg("--features").arg("web"); - } - _ => { - build.arg("--features").arg("desktop"); - } - } - - // these args are required to prevent DCE, save intermediates, and print the link args for future usage - // -all_load ensures all statics get bubbled out - // -link-dead-code prevents the flag `-Wl,-dead_strip` from being passed - // -save-temps ensures the intermediates are saved so we can use them for comparsions - build - .arg("--") - .arg("-Csave-temps=true") - .arg("-Clink-dead-code"); - - match target.architecture { - // usually just ld64 - uses your `cc` - target_lexicon::Architecture::Aarch64(_) => { - build.arg("-Clink-arg=-Wl,-all_load"); - } - - // we want "all-load", adjustable ifunc table, - target_lexicon::Architecture::Wasm32 => { - build.arg("-Clink-arg=--no-gc-sections"); - build.arg("-Clink-arg=--growable-table"); - build.arg("-Clink-arg=--whole-archive"); - build.arg("-Clink-arg=--export-table"); - build.arg("-Clink-arg=--export-memory"); - build.arg("-Clink-arg=--emit-relocs"); - build.arg("-Clink-arg=--export=__stack_pointer"); - build.arg("-Clink-arg=--export=__heap_base"); - build.arg("-Clink-arg=--export=__data_end"); - build.arg("-Crelocation-model=pic"); - } - - _ => {} - } - - // we capture the link args, but eventually we should actually just use ourselves as the linker since that's more robust - build - .arg("--print") - .arg("link-args") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .kill_on_drop(true) - .current_dir(workspace_dir()); - - let build = build.spawn()?; - - let out = run_cargo_output(build).await?; - - if target.architecture == target_lexicon::Architecture::Wasm32 { - _ = std::fs::remove_dir_all(static_folder()); - - let test_data_folder = wasm_data_folder(); - let _ = std::fs::create_dir_all(&test_data_folder); - let _ = std::fs::copy( - out.output_location.as_std_path(), - test_data_folder.join("pre-bindgen.wasm"), - ); - - let unprocessed = std::fs::read(out.output_location.as_std_path())?; - let all_exported_bytes = - subsecond_cli_support::prepare_wasm_base_module(&unprocessed).unwrap(); - let processed = test_data_folder.join("processed.wasm"); - std::fs::write(&processed, all_exported_bytes)?; - - let bind = Command::new("wasm-bindgen") - .arg("--target") - .arg("web") - .arg("--no-typescript") - .arg("--out-dir") - .arg(static_folder()) - .arg("--out-name") - .arg("main") - .arg("--no-demangle") - .arg("--keep-lld-exports") - .arg("--keep-debug") - .arg(&processed) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .kill_on_drop(true) - .current_dir(workspace_dir()) - .output() - .await?; - - let err = String::from_utf8(bind.stderr).unwrap(); - if !err.is_empty() { - tracing::error!("err: {err}"); - } - - let _ = std::fs::copy( - static_folder().join("main_bg.wasm"), - test_data_folder.join("post-bindgen.wasm"), - ); - - let index = include_str!("./index.html"); - std::fs::write(static_folder().join("index.html"), index).unwrap(); - } - - Ok(out) -} - -async fn fast_build( - original: &CargoOutputResult, - target: &Triple, - aslr_reference: u64, -) -> anyhow::Result { - let fast_build = Command::new(original.direct_rustc[0].clone()) - .args(original.direct_rustc[1..].iter()) - .arg("-C") - .arg(format!( - "linker={}", - std::env::current_exe().unwrap().display() - )) - .env("HOTRELOAD_LINK", "patch") - .env( - "CARGO_MANIFEST_DIR", - workspace_dir().join("packages/subsecond/subsecond-harness"), - ) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .current_dir(workspace_dir()) - .spawn()?; - - let output = run_cargo_output(fast_build).await?; - - tracing::info!("fast_build output: {output:#?}"); - - let link_args = std::fs::read_to_string(link_args_file())?; - let mut object_files = link_args - .lines() - .filter(|arg| arg.ends_with(".rcgu.o")) - .sorted() - .map(|arg| PathBuf::from(arg)) - .collect::>(); - - // copy incrementals to the data folder - if target.architecture == target_lexicon::Architecture::Wasm32 { - let test_data_folder = wasm_data_folder().join("incrementals"); - let _ = std::fs::create_dir_all(&test_data_folder); - for object in object_files.iter() { - let dest = test_data_folder.join(object.file_name().unwrap()); - std::fs::copy(object, dest)?; - } - } - - // on wasm we'll need to add in some symbols that resolve to the ifunc table - // unfortunately we can't quite call functions from main directly so we need to go through the ifunc system - // I *think* we can just import them - if target.architecture != target_lexicon::Architecture::Wasm32 { - let resolved = subsecond_cli_support::resolve_undefined( - &original.output_location.as_std_path(), - &object_files, - target, - aslr_reference, - ) - .unwrap(); - - let syms = subsecond_folder().join("data").join("syms.o"); - std::fs::write(&syms, resolved).unwrap(); - object_files.push(syms); - } - - let output_location = original - .output_location - .with_file_name(format!( - "patch-{}", - SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis() - )) - .with_extension(match target.architecture { - target_lexicon::Architecture::Wasm32 => "wasm", - _ => "", - }); - - let res = match target.architecture { - // usually just ld64 - uses your `cc` - target_lexicon::Architecture::Aarch64(_) => { - Command::new("cc") - .args(object_files) - .arg("-Wl,-dylib") - .arg("-arch") - .arg("arm64") - .arg("-Wl,-all_load") - // .arg("-Wl,-exported_symbol,__SECTION_START_MANGANIS") - .arg("-o") - .arg(&output_location) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await? - } - target_lexicon::Architecture::Wasm32 => { - // do not add "--emit-relocs"! this causes ld to segfault - Command::new(wasm_ld().await.unwrap()) - .args(object_files) - .arg("--import-memory") - .arg("--import-table") - .arg("--growable-table") - .arg("--export") - .arg("main") - .arg("--export-all") - .arg("--allow-undefined") - .arg("--no-demangle") - .arg("--no-entry") - .arg("--pie") - .arg("--experimental-pic") - .arg("-o") - .arg(&output_location) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await? - } - _ => todo!(), - }; - - let errs = String::from_utf8_lossy(&res.stderr); - if !errs.is_empty() { - tracing::error!("errs: {errs}"); - } - - tracing::info!("fast_build output: {output_location:?}"); - - Ok(output_location) -} - -/// Store the linker args in a file for the main process to read. -async fn link(action: String) -> anyhow::Result<()> { - let args = std::env::args().collect::>(); - - // Write the linker args to a file for the main process to read - std::fs::write(link_args_file(), args.join("\n"))?; - - match action.as_str() { - // Actually link the object file. todo: figure out which linker we should be using - "link" => {} - - // Write a dummy object file to the output file to satisfy rust when it tries to strip the symbols - "patch" => { - let out = args.iter().position(|arg| arg == "-o").unwrap(); - let out_file = args[out + 1].clone(); - let host = Triple::host(); - let dummy_object_file = Object::new( - match host.binary_format { - target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf, - target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff, - target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO, - target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm, - target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff, - _ => todo!(), - }, - match host.architecture { - target_lexicon::Architecture::Arm(_) => object::Architecture::Arm, - target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64, - target_lexicon::Architecture::X86_32(_) => object::Architecture::X86_64_X32, - target_lexicon::Architecture::X86_64 => object::Architecture::X86_64, - _ => todo!(), - }, - match host.endianness().unwrap() { - target_lexicon::Endianness::Little => object::Endianness::Little, - target_lexicon::Endianness::Big => object::Endianness::Big, - }, - ); - let bytes = dummy_object_file.write().unwrap(); - std::fs::write(out_file, bytes)?; - } - - _ => anyhow::bail!("Unknown action: {}", action), - } - - Ok(()) -} - -#[derive(Debug)] -struct CargoOutputResult { - output_location: Utf8PathBuf, - direct_rustc: Vec, -} - -async fn run_cargo_output(mut child: Child) -> anyhow::Result { - let stdout = tokio::io::BufReader::new(child.stdout.take().unwrap()); - let stderr = tokio::io::BufReader::new(child.stderr.take().unwrap()); - let mut output_location = None; - let mut stdout = stdout.lines(); - let mut stderr = stderr.lines(); - - let mut direct_rustc = vec![]; - - loop { - use cargo_metadata::Message; - - let line = tokio::select! { - Ok(Some(line)) = stdout.next_line() => line, - Ok(Some(line)) = stderr.next_line() => line, - else => break, - }; - - let mut messages = Message::parse_stream(std::io::Cursor::new(line)); - - loop { - let message = match messages.next() { - Some(Ok(message)) => message, - None => break, - other => { - tracing::trace!("other: {other:?}"); - break; - } - }; - - match message { - Message::CompilerArtifact(artifact) => { - if let Some(i) = artifact.executable { - output_location = Some(i) - } - } - Message::CompilerMessage(compiler_message) => { - if let Some(rendered) = &compiler_message.message.rendered { - tracing::trace!("rendered: {rendered}"); - } - } - Message::BuildScriptExecuted(_build_script) => {} - Message::BuildFinished(build_finished) => { - // assuming we received a message from the compiler, so we can exit - if !build_finished.success { - anyhow::bail!("Build failed"); - } - } - Message::TextLine(word) => { - // trim everyting but the contents between the quotes - if word.trim().starts_with("Running ") { - let args = word - .trim() - .trim_start_matches("Running `") - .trim_end_matches('`'); - direct_rustc = shell_words::split(args).unwrap(); - } - - #[derive(Debug, Deserialize)] - struct RustcArtifact { - artifact: PathBuf, - emit: String, - } - - if let Ok(artifact) = serde_json::from_str::(&word) { - if artifact.emit == "link" { - output_location = - Some(Utf8PathBuf::from_path_buf(artifact.artifact).unwrap()); - } - } - - tracing::trace!("text: {word}") - } - _ => {} - } - } - } - - let output_location = - output_location.context("Failed to find output location. Build must've failed.")?; - - Ok(CargoOutputResult { - output_location, - direct_rustc, - }) -} - -struct FsWatcher { - _watcher: notify::RecommendedWatcher, - files: HashMap, - rx: futures_channel::mpsc::UnboundedReceiver>, -} - -impl FsWatcher { - fn watch(src_folder: PathBuf) -> anyhow::Result { - let (tx, rx) = futures_channel::mpsc::unbounded(); - let mut watcher = - notify::recommended_watcher(move |res: notify::Result| { - _ = tx.unbounded_send(res); - })?; - - let mut files = HashMap::new(); - for entry in walkdir::WalkDir::new(src_folder) { - let entry = entry?; - let path = entry.path(); - if path.is_dir() || path.extension() != Some(OsStr::new("rs")) { - continue; - } - files.insert(path.to_path_buf(), std::fs::read_to_string(&path).unwrap()); - watcher.watch(&path, notify::RecursiveMode::NonRecursive)?; - } - - Ok(FsWatcher { - files, - rx, - _watcher: watcher, - }) - } - - /// Check if the file has changed and update the internal state - fn file_changed(&mut self, path: &PathBuf) -> bool { - if let Some(contents) = self.files.get_mut(path) { - let new_contents = std::fs::read_to_string(&path).unwrap(); - if new_contents == *contents { - return false; - } - *contents = new_contents; - return true; - } - - false - } -} - -struct WsClient { - aslr_reference: u64, - socket: WebSocketStream, -} - -async fn wait_for_ws(port: u16) -> anyhow::Result> { - let addr = format!("127.0.0.1:{}", port); - let try_socket = TcpListener::bind(&addr).await; - let listener = try_socket.expect("Failed to bind"); - - let (conn, _sock) = listener.accept().await?; - let mut socket = tokio_tungstenite::accept_async(conn).await?; - let msg = socket.next().await.unwrap()?; - let aslr_reference = msg.into_text().unwrap().parse().unwrap(); - - Ok(Some(WsClient { - aslr_reference, - socket, - })) -} - -async fn wasm_ld() -> anyhow::Result { - // eg. /Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld - // |_________________________sysroot_____________________________| - // - // we should opt to use rust-lld since that's the default on linux and will eventually be the default on windows - // I think mac will keep ld - let root = Command::new("rustc") - .arg("--print") - .arg("sysroot") - .output() - .await?; - let root = PathBuf::from(String::from_utf8(root.stdout)?.trim()) - .join("lib") - .join("rustlib") - .join(Triple::host().to_string()) - .join("bin") - .join("gcc-ld") - .join("wasm-ld"); - Ok(root) -} - -fn workspace_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../../") - .canonicalize() - .unwrap() -} - -fn subsecond_folder() -> PathBuf { - workspace_dir().join("packages").join("subsecond") -} - -fn wasm_data_folder() -> PathBuf { - subsecond_folder().join("data").join("wasm") -} - -fn static_folder() -> PathBuf { - subsecond_folder().join("subsecond-harness").join("static") -} - -fn link_args_file() -> PathBuf { - subsecond_folder().join("data").join("link.txt") -} diff --git a/packages/subsecond/subsecond-harness/Cargo.toml b/packages/subsecond/subsecond-harness/Cargo.toml deleted file mode 100644 index 9cc507dbc3..0000000000 --- a/packages/subsecond/subsecond-harness/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "subsecond-harness" -edition = "2021" -version.workspace = true - -[dependencies] -dioxus = { workspace = true } -anyhow = { workspace = true } -subsecond = { workspace = true } -bincode = { workspace = true } -serde_json = { workspace = true } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -rand = { workspace = true } -color-eyre = { workspace = true } -ratatui = { workspace = true, features = ["crossterm"] } -tungstenite = { version = "0.23.0" } -tokio = { workspace = true, features = ["full"] } - -[features] -default = [] -fullstack = [ "dioxus/fullstack"] -desktop = [ "dioxus/desktop" ] -mobile = [ "dioxus/mobile" ] -web = [ "dioxus/web" ] -server = ["dioxus/server"] diff --git a/packages/subsecond/subsecond-harness/README.md b/packages/subsecond/subsecond-harness/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/subsecond/subsecond-harness/assets/test.css b/packages/subsecond/subsecond-harness/assets/test.css deleted file mode 100644 index c256a7964b..0000000000 --- a/packages/subsecond/subsecond-harness/assets/test.css +++ /dev/null @@ -1,12 +0,0 @@ -#external-links { - display: flex; - flex-direction: column; -} - -#nav { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - background-color: #f4f4f4; -} diff --git a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs b/packages/subsecond/subsecond-harness/src/dioxus_demo.rs deleted file mode 100644 index f71190fd6f..0000000000 --- a/packages/subsecond/subsecond-harness/src/dioxus_demo.rs +++ /dev/null @@ -1,177 +0,0 @@ -use dioxus::prelude::*; - -pub fn launch() { - dioxus::launch(app); -} - -static CSS1: Asset = asset!("/assets/test.css"); -static CSS2: Asset = asset!("/assets/test.css"); -static CSS3: Asset = asset!("/assets/test.css"); - -fn app() -> Element { - let mut items: Signal> = use_signal(|| vec![]); - - rsx! { - div { - link { href: CSS1, rel: "stylesheet" } - link { href: CSS2, rel: "stylesheet" } - link { href: CSS3, rel: "stylesheet" } - h1 { "Build a todo list!" } - h1 { "Build a todo list!" } - h1 { "Build a todo list!" } - h1 { "Build a todo list!" } - button { - onclick: move |_| async move { - // let res = request_item(123).await.unwrap(); - let res = "Item".to_string(); - items.write().push(res); - }, - "Add Item" - } - - - button { - onclick: move |_| { - items.write().clear(); - }, - "Clear Items" - } - - - for item in items.iter() { - li { "{item}" } - } - } - } -} - -// #[server(endpoint = "request_item")] -// pub async fn request_item(val: i32) -> Result { -// dioxus::subsecond::call(|| { -// Ok("hotpatchy frontend frontend frontend frontend frontend!!!".to_string()) -// }) -// } - -// fn app() -> Element { -// let mut count = use_signal(|| 0); - -// rsx! { -// div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", -// h1 { "Apple: {count} ???" } -// button { onclick: move |_| count += 1, "Incr" } -// button { onclick: move |_| count -= 1, "Decr" } -// img { -// width: "300px", -// src: "https://rustacean.net/assets/rustacean-flat-happy.png", -// } -// } -// div { style: "display: flex; flex-direction: column; align-items: center; justify-content: center;", -// div { style: "background-color: red", -// for x in 0..1 { -// Child { id: x + 1, opt: "List entry", color: "gri" } -// } -// } -// div { style: "background-color: orange", -// for x in 0..1 { -// Child { id: x + 1, opt: "List entry", color: "blue" } -// } -// } -// div { style: "background-color: yellow", -// for x in 0..1 { -// Child { id: x + 1, opt: "List entry", color: "yellow" } -// } -// } -// div { style: "background-color: green", -// for x in 0..1 { -// Child { id: x + 10, opt: "List entry", color: "orange" } -// } -// } -// div { style: "background-color: blue", -// for x in 0..1 { -// Child { id: x + 10, opt: "List entry", color: "bluebleu" } -// } -// } -// div { style: "background-color: indigo", -// for x in 0..1 { -// Child { id: x + 10, opt: "List entry", color: "magentaaa" } -// } -// } -// } -// } -// } - -// #[component] -// fn Child(id: u32, opt: String, color: String) -> Element { -// let mut count = use_signal(|| 0); - -// rsx! { -// div { -// h3 { "Chil!!!!!!!!!! {id} - {opt} - {color} - {color} - {color}" } -// p { "count: {count}" } -// button { -// onclick: move |_| { -// count += id; -// }, -// "Increment Count" -// } -// } -// } -// } -// #[component] -// fn Child2(id: u32, opt: String) -> Element { -// rsx! { -// div { "oh lordy!" } -// div { "Hello ?? child2s: {id} - {opt} ?" } -// } -// } - -// #[component] -// fn Child3(id: u32, opt: String) -> Element { -// rsx! { -// div { "Hello ?? child: {id} - {opt} ?" } -// } -// } - -// #[component] -// fn Child4(id: u32, opt: String) -> Element { -// rsx! { -// div { "Hello ?? child: {id} - {opt} ?" } -// div { "Hello ?? child: {id} - {opt} ?" } -// div { "Hello ?? child: {id} - {opt} ?" } -// } -// } - -// #[component] -// fn ZoomComponent() -> Element { -// // use dioxus::desktop::window; -// // button { onclick: move |_| window().set_zoom_level(1.0), "Zoom 1x" } -// // button { onclick: move |_| window().set_zoom_level(1.5), "Zoom 1.5x" } -// // button { onclick: move |_| window().set_zoom_level(2.0), "Zoom 2x" } -// // button { onclick: move |_| window().set_zoom_level(3.0), "Zoom 3x" } -// rsx! { -// div { "Zoom me!" } -// } -// } - -// fn app() -> Element { -// let mut items: Signal> = use_signal(|| vec![]); - -// rsx! { -// div { -// h1 { "Build a todo list!" } -// h1 { "Build a todo list!" } -// h1 { "Build a todo list!" } -// button { -// onclick: move |_| async move { -// // let res = request_item(123).await.unwrap(); -// items.write().push("Item".to_string()); -// }, -// "Add Item" -// } - -// for item in items.iter() { -// li { "{item}" } -// } -// } -// } -// } diff --git a/packages/subsecond/subsecond-harness/src/loop_demo.rs b/packages/subsecond/subsecond-harness/src/loop_demo.rs deleted file mode 100644 index 50192fe873..0000000000 --- a/packages/subsecond/subsecond-harness/src/loop_demo.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub fn launch() { - loop { - std::thread::sleep(std::time::Duration::from_secs(1)); - subsecond::call(|| tick()); - } -} - -fn tick() { - println!("edit me to see the loop in action!!!!!!!!! "); -} diff --git a/packages/subsecond/subsecond-harness/src/main.rs b/packages/subsecond/subsecond-harness/src/main.rs deleted file mode 100644 index d67ed4a4b5..0000000000 --- a/packages/subsecond/subsecond-harness/src/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -mod dioxus_demo; -mod loop_demo; -mod tui_demo; -mod ws_conn; - -#[cfg(not(target_arch = "wasm32"))] -fn main() { - ws_conn::initialize(); - - let demo = std::env::var("DEMO").unwrap_or("dioxus".to_string()); - - match demo.as_str() { - "dioxus" => dioxus_demo::launch(), - "loop" => loop_demo::launch(), - "tui" => tui_demo::launch(), - _ => panic!("Unknown demo: {}", demo), - } -} - -#[cfg(target_arch = "wasm32")] -fn main() { - dioxus_demo::launch(); -} diff --git a/packages/subsecond/subsecond-harness/src/tui_demo.rs b/packages/subsecond/subsecond-harness/src/tui_demo.rs deleted file mode 100644 index 81e6f1fbdc..0000000000 --- a/packages/subsecond/subsecond-harness/src/tui_demo.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![cfg(not(target_arch = "wasm32"))] - -use color_eyre::Result; -use rand::{rng, Rng}; -use ratatui::{ - crossterm::event::{self, Event, KeyCode, KeyEventKind}, - layout::{Constraint, Layout}, - style::{Color, Style, Stylize}, - text::Line, - widgets::{Bar, BarChart, BarGroup}, - DefaultTerminal, Frame, -}; -use std::time::Duration; - -pub fn launch() { - color_eyre::install().unwrap(); - let mut terminal = ratatui::init(); - let app_result = subsecond::call(|| App::new().run(&mut terminal)); - ratatui::restore(); - app_result.unwrap(); -} - -struct App { - should_exit: bool, - temperatures: Vec, -} - -impl App { - fn new() -> Self { - let mut rng = rand::rng(); - let temperatures = (0..24).map(|_| rng.random_range(50..90)).collect(); - - Self { - should_exit: false, - temperatures, - } - } - - fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> { - while !self.should_exit { - subsecond::call(|| self.tick(terminal))?; - } - Ok(()) - } - - fn tick(&mut self, terminal: &mut DefaultTerminal) -> Result<()> { - terminal.draw(|frame| self.draw(frame))?; - self.handle_events()?; - Ok(()) - } - - // wait 100ms for an event to occur - fn handle_events(&mut self) -> Result<()> { - if event::poll(Duration::from_millis(100))? { - if let Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { - self.should_exit = true; - } - - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('t') { - let mut rng = rng(); - self.temperatures = (0..24).map(|_| rng.random_range(50..90)).collect(); - } - } - } - - Ok(()) - } - - fn draw(&self, frame: &mut Frame) { - let [title, main] = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]) - .spacing(1) - .areas(frame.area()); - - frame.render_widget( - "Tui development has never been so easy!" - .bold() - .italic() - .into_centered_line() - .centered(), - title, - ); - frame.render_widget(vertical_barchart(&self.temperatures), main); - } -} - -/// Create a vertical bar chart from the temperatures data. -fn vertical_barchart(temperatures: &[u8]) -> BarChart { - let bars: Vec = temperatures - .iter() - .enumerate() - .map(|(hour, value)| vertical_bar(hour, value)) - .collect(); - BarChart::default() - .data(BarGroup::default().bars(&bars)) - .bar_width(5) -} - -fn vertical_bar(hour: usize, temperature: &u8) -> Bar { - Bar::default() - .value(u64::from(*temperature)) - .label(Line::from(format!("{hour:>02}:00"))) - .text_value(format!("{temperature:>3}°")) - .style(temperature_style(*temperature)) - .value_style(temperature_style(*temperature).reversed()) -} - -/// create a yellow to red value based on the value (50-90) -fn temperature_style(value: u8) -> Style { - let green = (255.0 * (1.0 - f64::from(value - 50) / 40.0)) as u8; - let color = Color::Rgb(255, green, 0); - Style::new().fg(color) -} diff --git a/packages/subsecond/subsecond-harness/src/ws_conn.rs b/packages/subsecond/subsecond-harness/src/ws_conn.rs deleted file mode 100644 index 0ccf4112ce..0000000000 --- a/packages/subsecond/subsecond-harness/src/ws_conn.rs +++ /dev/null @@ -1,35 +0,0 @@ -use subsecond::JumpTable; - -pub fn initialize() { - // dx already has subsecond integrated, don't boot it twice - if dioxus::cli_config::devserver_ws_endpoint().is_some() { - return; - } - - // Spawn a thread that will read bytes from the fd - // the host process will write newa bytes to the fd when it wants to reload the binary - #[cfg(not(target_arch = "wasm32"))] - std::thread::spawn(|| { - let endpoint = - std::env::var("HOTRELOAD_ENDPOINT").unwrap_or("ws://localhost:9393".to_string()); - - let (mut websocket, _req) = match tungstenite::connect(endpoint.clone()) { - Ok((websocket, req)) => (websocket, req), - Err(_) => panic!("Failed to connect to hotreload endpoint"), - }; - - websocket - .send(tungstenite::Message::Text( - subsecond::aslr_reference().to_string(), - )) - .unwrap(); - - while let Ok(msg) = websocket.read() { - if let tungstenite::Message::Text(bytes) = msg { - if let Ok(msg) = serde_json::from_str::(bytes.as_ref()) { - unsafe { subsecond::apply_patch(msg) }; - } - } - } - }); -} diff --git a/packages/subsecond/subsecond-macro/Cargo.toml b/packages/subsecond/subsecond-macro/Cargo.toml deleted file mode 100644 index 7877565e70..0000000000 --- a/packages/subsecond/subsecond-macro/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "subsecond-macro" -edition = "2021" -version.workspace = true - -[dependencies] -syn = { workspace = true, features = ["full"] } -base16 = { workspace = true } -digest = { workspace = true } -quote = { workspace = true } -sha2 = { workspace = true } -proc-macro2 = { workspace = true } - -[lib] -proc-macro = true diff --git a/packages/subsecond/subsecond-macro/src/lib.rs b/packages/subsecond/subsecond-macro/src/lib.rs deleted file mode 100644 index 8bd873ca1b..0000000000 --- a/packages/subsecond/subsecond-macro/src/lib.rs +++ /dev/null @@ -1,167 +0,0 @@ -use proc_macro::TokenStream; - -use digest::Digest; -use quote::{format_ident, quote, ToTokens}; -use syn::{parse_macro_input, parse_quote, FnArg, Ident, ItemFn, PatIdent, ReturnType, Signature}; - -/// Annotate a function with `#[hot]` to make it hot-reloadable. -/// -/// This can be used on functions and methods. Changes to the assembly "beneath" the function will -/// cause the function to be recompiled and the new assembly to be executed. -/// -/// If the changes propagate above the function, the nearest `#[hot]` function will be used as the -/// hot-reload point. -/// -/// ``` -/// struct Foo {} -/// -/// impl Foo { -/// #[hot] -/// fn tick(&mut self) { -/// self.do_stuff() -/// } -/// } -/// ``` -/// -/// ## Expansion: -/// -/// This macro simply expands functions from the following form: -/// -/// ```rust -/// #[hot] -/// fn do_thing(a: A, b: B) -> C { -/// } -/// ``` -/// -/// to the following: -/// -/// ```rust -/// fn do_thing(a: A, b: B) -> C { -/// #[inline(never)] // force this as a real symbol -/// fn __hot_do_thing(a: A, b: B) -> C { -/// do_thing_inner(a, b) -/// } -/// -/// subsecond::current(do_thing_inner).call((a, b)) -/// } -/// ``` -/// -/// You could also just call `subsecond::current()` yourself, though that interface is slightly -/// unwieldy and intended for use by framework authors. -#[proc_macro_attribute] -pub fn hot(_args: TokenStream, input: TokenStream) -> TokenStream { - /* - #[hot] - fn do_thing(a: A, b: B) -> C { - } - - // expands to - - fn do_thing(a: A, b: B) -> C { - #[inline(never)] // force this as a real symbol - fn __hot_do_thing(a: A, b: B) -> C { - do_thing_inner(a, b) - } - - subsecond::current(do_thing_inner).call((a, b)) - } - - - // for methods, we don't know the type of the receiver, so we generate another method that's hidden - // that also takes `self` as an argument - // - // note that we want to retain the names of idents so rust-analyzer provides the correct info - - struct Foo {} - impl Foo { - #[hot] - fn do_thing(&self, a: A, b: B) -> C { - // code... - } - - // expands to - fn do_thing(&self, a: A, b: B) -> C { - subsecond::current(Self::__hot_do_thing).call((self, a, b)) - } - - fn __hot_do_thing(&self, a: A, b: B) -> C { - // code... - } - } - */ - - let ItemFn { - attrs, - vis, - sig, - block, - } = parse_macro_input!(input as ItemFn); - - let mut outer_sig = sig.clone(); - let mut inner_sig = sig.clone(); - inner_sig.ident = format_ident!("__hot_{}", sig.ident); - - let inner_fn_name = inner_sig.ident.clone(); - - let mut args = vec![]; - for (i, param) in outer_sig.inputs.iter_mut().enumerate() { - match param { - syn::FnArg::Receiver(_) => args.push(format_ident!("self")), - syn::FnArg::Typed(pat_type) => { - match &*pat_type.pat { - // Attempt to preserve original ident for better RA support - syn::Pat::Ident(pat_ident) => { - args.push(pat_ident.ident.clone()); - } - - // Otherwise, generate a new ident - _ => { - // Create a new ident to tie the outer to the call of the inner - let param_ident = format_ident!("__hot_arg_{i}"); - args.push(param_ident.clone()); - pat_type.pat = Box::new(syn::Pat::Ident(syn::PatIdent { - attrs: vec![], - by_ref: None, - mutability: None, - ident: param_ident, - subpat: None, - })); - } - } - } - } - } - - let self_ident = if outer_sig - .inputs - .first() - .map(|arg| matches!(arg, FnArg::Receiver(_))) - == Some(true) - { - quote! { Self:: } - } else { - quote! {} - }; - - quote! { - // the primary function - // &self, Pattern { a, b, c}: i32, b: i32, c: i32, etc - // becomes - // self: &mut Self, arg0: i32, arg1: i32, arg2: i32, etc - #(#attrs)* - #vis #outer_sig { - subsecond::current(#self_ident #inner_fn_name).call( - (#(#args),*) // .call((self, arg0, arg1)) - ) - } - - // retains the original function signature - // &self, a: i32, b: i32, c: i32, etc - #[doc(hidden)] - #[inline(never)] - #inner_sig { - #block - } - } - .into() -} From 55f277ea5953dce928435cbc165648f1f1c78272 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 00:08:34 -0700 Subject: [PATCH 134/301] fix compile --- Cargo.lock | 267 +----------------------- Cargo.toml | 1 - packages/subsecond/subsecond/Cargo.toml | 1 - 3 files changed, 6 insertions(+), 263 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c777fb9686..b4a35b4741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,7 +1175,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.2", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite", "tower 0.5.2", "tower-layer", "tower-service", @@ -2441,33 +2441,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c387f6cef110ee8eaf12fca5586d3d303c07c594f4a5f02c768b6470b70dbd" -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors 3.5.0", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors 3.5.0", - "tracing-core", - "tracing-error", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -3659,7 +3632,7 @@ name = "dioxus-check" version = "0.6.3" dependencies = [ "indoc", - "owo-colors 4.2.0", + "owo-colors", "pretty_assertions", "proc-macro2", "quote", @@ -3871,7 +3844,7 @@ dependencies = [ "slab", "slotmap", "subsecond", - "sysinfo 0.33.1", + "sysinfo", "tokio", "tracing", "tracing-fluent-assertions", @@ -3952,7 +3925,7 @@ dependencies = [ "tao", "thiserror 2.0.12", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite", "tracing", "tray-icon", "urlencoding", @@ -5166,28 +5139,12 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "faster-hex" version = "0.9.0" @@ -5908,7 +5865,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ - "fallible-iterator 0.2.0", + "fallible-iterator", "indexmap 1.9.3", "stable_deref_trait", ] @@ -5919,17 +5876,6 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -dependencies = [ - "fallible-iterator 0.3.0", - "indexmap 2.9.0", - "stable_deref_trait", -] - [[package]] name = "gio" version = "0.18.4" @@ -7430,12 +7376,6 @@ dependencies = [ "quote", ] -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" version = "1.9.3" @@ -8457,36 +8397,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "macext" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e7c68cadc41a5342a826858a31b0bd656447b4306873628e93ffb807009599" -dependencies = [ - "mach2", - "process-memory", - "sudo", - "sysinfo 0.30.13", -] - -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "macro-string" version = "0.1.4" @@ -9882,12 +9792,6 @@ dependencies = [ "ttf-parser 0.25.1", ] -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "owo-colors" version = "4.2.0" @@ -9953,16 +9857,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "pango" version = "0.18.3" @@ -10769,17 +10663,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "process-memory" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9599c34fcc8067c3105dc746c0ce85e3ea61784568b8234179fad490b1dcc1" -dependencies = [ - "libc", - "mach", - "winapi", -] - [[package]] name = "prodash" version = "28.0.0" @@ -12390,7 +12273,7 @@ dependencies = [ "thiserror 2.0.12", "throw_error", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite", "tower 0.5.2", "tower-layer", "url", @@ -13419,7 +13302,6 @@ dependencies = [ "memmap2", "serde", "serde-wasm-bindgen", - "subsecond-macro", "subsecond-types", "thiserror 2.0.12", "wasm-bindgen", @@ -13427,50 +13309,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "subsecond-cli" -version = "0.6.3" -dependencies = [ - "anyhow", - "axum 0.8.3", - "axum-extra", - "axum-server", - "bincode", - "cargo_metadata", - "clap", - "crossterm 0.29.0", - "futures", - "futures-channel", - "futures-util", - "gimli 0.31.1", - "include_dir", - "itertools 0.14.0", - "libc", - "libloading 0.8.6", - "macext", - "memmap", - "notify", - "object 0.36.7", - "ouroboros", - "page_size", - "pretty-hex", - "pretty_assertions", - "rustc-demangle", - "serde", - "serde_json", - "shell-words", - "subsecond-cli-support", - "sysinfo 0.33.1", - "target-lexicon 0.13.2", - "tokio", - "tokio-tungstenite 0.23.1", - "tower-http", - "tracing", - "tracing-subscriber", - "urlencoding", - "walkdir", -] - [[package]] name = "subsecond-cli-support" version = "0.6.3" @@ -13499,34 +13337,6 @@ dependencies = [ "wasmparser 0.226.0", ] -[[package]] -name = "subsecond-harness" -version = "0.6.3" -dependencies = [ - "anyhow", - "bincode", - "color-eyre", - "dioxus", - "rand 0.8.5", - "ratatui", - "serde_json", - "subsecond", - "tokio", - "tungstenite 0.23.0", -] - -[[package]] -name = "subsecond-macro" -version = "0.6.3" -dependencies = [ - "base16", - "digest", - "proc-macro2", - "quote", - "sha2", - "syn 2.0.100", -] - [[package]] name = "subsecond-types" version = "0.6.3" @@ -13540,16 +13350,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "sudo" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bd84d4c082e18e37fef52c0088e4407dabcef19d23a607fb4b5ee03b7d5b83" -dependencies = [ - "libc", - "log", -] - [[package]] name = "supports-color" version = "2.1.0" @@ -14118,21 +13918,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "sysinfo" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - [[package]] name = "sysinfo" version = "0.33.1" @@ -14757,18 +14542,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "tokio-tungstenite" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.23.0", -] - [[package]] name = "tokio-tungstenite" version = "0.26.2" @@ -15000,16 +14773,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-fluent-assertions" version = "0.3.0" @@ -15174,24 +14937,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.3.1", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.26.2" diff --git a/Cargo.toml b/Cargo.toml index f3be320c56..02202750fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,6 @@ lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.6.2" } # subsecond subsecond-cli-support = { path = "packages/subsecond/subsecond-cli-support", version = "0.6.3" } subsecond-types = { path = "packages/subsecond/subsecond-types", version = "0.6.3" } -subsecond-macro = { path = "packages/subsecond/subsecond-macro", version = "0.6.3" } subsecond = { path = "packages/subsecond/subsecond", version = "0.6.3" } # manganis diff --git a/packages/subsecond/subsecond/Cargo.toml b/packages/subsecond/subsecond/Cargo.toml index e4919f0f66..dd720629d2 100644 --- a/packages/subsecond/subsecond/Cargo.toml +++ b/packages/subsecond/subsecond/Cargo.toml @@ -5,7 +5,6 @@ version.workspace = true [dependencies] serde = { version = "1.0.203", features = ["derive"] } -subsecond-macro = { workspace = true } subsecond-types = { workspace = true } thiserror = { workspace = true } From 073735cb7001cf0f8e18a4ee7f2d1c4aca0fc7d0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 12:07:36 -0700 Subject: [PATCH 135/301] anonymize some files, heavily document others --- packages/cli/src/build/builder.rs | 6 +- packages/cli/src/build/request.rs | 488 ++++++-------- packages/cli/src/cli/build.rs | 6 +- packages/cli/src/cli/bundle.rs | 6 +- packages/cli/src/cli/link.rs | 202 +++--- packages/cli/src/cli/run.rs | 4 +- packages/cli/src/main.rs | 6 +- packages/cli/src/rustcwrapper.rs | 37 +- packages/cli/src/serve/mod.rs | 8 +- packages/cli/src/serve/runner.rs | 41 +- packages/depinfo/src/dx.d | 2 +- packages/depinfo/src/lib.rs | 634 +++++++++--------- packages/server/src/launch.rs | 22 +- packages/server/src/rt.rs | 2 +- .../subsecond-cli-support/src/lib.rs | 25 - packages/subsecond/subsecond/src/lib.rs | 1 - 16 files changed, 689 insertions(+), 801 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 4b36b4232f..abb3dac51c 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -116,7 +116,7 @@ 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) -> Result { + pub(crate) fn start(request: &BuildRequest, mode: BuildMode) -> Result { let (tx, rx) = futures_channel::mpsc::unbounded(); Ok(Self { @@ -127,8 +127,8 @@ impl AppBuilder { let tx = tx.clone(); async move { let ctx = BuildContext { + mode, tx: tx.clone(), - mode: BuildMode::Fat, }; request.verify_tooling(&ctx).await?; request.prepare_build_dir()?; @@ -274,7 +274,7 @@ impl AppBuilder { tx: self.tx.clone(), mode: BuildMode::Thin { changed_files, - direct_rustc: self.artifacts.as_ref().unwrap().direct_rustc.clone(), + rustc_args: self.artifacts.as_ref().unwrap().direct_rustc.clone(), aslr_reference: self.aslr_reference, }, }; diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 381e1b6d37..531848234b 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -227,10 +227,12 @@ //! ## Extra links //! - xbuild: https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs -use super::{AndroidTools, BuildContext, BuildId}; +use super::{android_tools, AndroidTools, BuildContext, BuildId}; use crate::{ - rustcwrapper::RustcArgs, wasm_bindgen::WasmBindgen, BuildArgs, DioxusConfig, Error, LinkAction, - Platform, ProgressTx, Result, TraceSrc, WasmOptConfig, Workspace, + rustcwrapper::{RustcArgs, DX_RUSTC_WRAPPER_ENV_VAR}, + wasm_bindgen::WasmBindgen, + BuildArgs, DioxusConfig, Error, LinkAction, Platform, ProgressTx, Result, TraceSrc, + WasmOptConfig, Workspace, }; use anyhow::Context; use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV}; @@ -258,6 +260,7 @@ use target_lexicon::{ Aarch64Architecture, Architecture, ArmArchitecture, BinaryFormat, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture, }; +use tempfile::NamedTempFile; use tokio::{io::AsyncBufReadExt, process::Command}; use toml_edit::Item; use uuid::Uuid; @@ -324,10 +327,31 @@ pub(crate) struct BuildRequest { pub(crate) debug_symbols: bool, pub(crate) inject_loading_scripts: bool, + + pub(crate) custom_linker: Option, + + pub(crate) link_args_file: Arc, + + pub(crate) link_err_file: Arc, + + pub(crate) rustc_wrapper_args_file: Arc, } /// dx can produce different "modes" of a build. A "regular" build is a "base" build. The Fat and Thin /// modes are used together to achieve binary patching and linking. +/// +/// Guide: +/// ---------- +/// - Base: A normal build generated using `cargo rustc`, intended for production use cases +/// +/// - Fat: A "fat" build with -Wl,-all_load and no_dead_strip, keeping *every* symbol in the binary. +/// Intended for development for larger up-front builds with faster link times and the ability +/// to binary patch the final binary. On WASM, this also forces wasm-bindgen to generate all +/// JS-WASM bindings, saving us the need to re-wasmbindgen the final binary. +/// +/// - Thin: A "thin" build that dynamically links against the dependencies produced by the "fat" build. +/// This is generated by calling rustc *directly* and might be more fragile to construct, but +/// generates *much* faster than a regular base or fat build. #[derive(Clone, Debug, PartialEq)] pub enum BuildMode { /// A normal build generated using `cargo rustc` @@ -338,7 +362,7 @@ pub enum BuildMode { /// A "thin" build generated with `rustc` directly and dx as a custom linker Thin { - direct_rustc: RustcArgs, + rustc_args: RustcArgs, changed_files: Vec, aslr_reference: Option, }, @@ -381,7 +405,7 @@ impl BuildRequest { /// /// Note: Build requests are typically created only when the CLI is invoked or when significant /// changes are detected in the `Cargo.toml` (e.g., features added or removed). - pub async fn new(args: &BuildArgs) -> Result { + pub(crate) async fn new(args: &BuildArgs) -> Result { let workspace = Workspace::current().await?; let crate_package = workspace.find_main_package(args.package.clone())?; @@ -504,10 +528,10 @@ impl BuildRequest { // We want a real triple to build with, so we'll autodetect it if it's not provided // The triple ends up being a source of truth for us later hence all this work to figure it out - let target = match args.target.clone() { + let triple = match args.target.clone() { Some(target) => target, None => match platform { - // Generally just use the host's triple for native executables unless specified otherwisea + // Generally just use the host's triple for native executables unless specified otherwise Platform::MacOS | Platform::Windows | Platform::Linux @@ -541,6 +565,25 @@ impl BuildRequest { }, }; + let custom_linker = if platform == Platform::Android { + let tools = android_tools().context("Failed to find your Android NDK setup")?; + Some(tools.android_cc(&triple)) + } else { + None + }; + + // Set up some tempfiles so we can do some IPC between us and the linker (which is occasionally us!) + let link_args_file = Arc::new( + NamedTempFile::new().context("Failed to create temporary file for linker args")?, + ); + let link_err_file = Arc::new( + NamedTempFile::new().context("Failed to create temporary file for linker args")?, + ); + let rustc_wrapper_args_file = Arc::new( + NamedTempFile::new() + .context("Failed to create temporary file for rustc wrapper args")?, + ); + Ok(Self { platform, features, @@ -548,11 +591,15 @@ impl BuildRequest { crate_package, crate_target, profile, - triple: target, + triple, device, workspace, config, custom_target_dir: None, + custom_linker, + link_args_file, + link_err_file, + rustc_wrapper_args_file, cargo_args: args.cargo_args.clone(), nightly: args.nightly, cargo_package: package, @@ -650,18 +697,6 @@ impl BuildRequest { match message { Message::BuildScriptExecuted(_) => units_compiled += 1, Message::TextLine(line) => { - // // Try to extract the direct rustc args from the output - // if line.trim().starts_with("Running ") { - // // trim everyting but the contents between the quotes - // let args = line - // .trim() - // .trim_start_matches("Running `") - // .trim_end_matches('`'); - - // // Parse these as shell words so we can get the direct rustc args - // direct_rustc = shell_words::split(args).unwrap(); - // } - #[derive(Deserialize)] struct RustcArtifact { artifact: PathBuf, @@ -722,11 +757,12 @@ impl BuildRequest { let mode = ctx.mode.clone(); tracing::debug!("Build completed successfully - output location: {:?}", exe); + // 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()) { - let res: crate::rustcwrapper::RustcArgs = serde_json::from_str(&res).unwrap(); - direct_rustc = res; + if let Ok(res) = std::fs::read_to_string(self.rustc_wrapper_args_file.path()) { + if let Ok(res) = serde_json::from_str(&res) { + direct_rustc = res; + } } Ok(BuildArtifacts { @@ -740,13 +776,6 @@ impl BuildRequest { }) } - fn rustc_wrapper_args_file(&self) -> PathBuf { - // self.platform_dir() - PathBuf::from_str("/Users/jonkelley/Development/dioxus/packages/subsecond/data/") - .unwrap() - .join(format!("rustc_args-{}.json", self.triple)) - } - /// Traverse the target directory and collect all assets from the incremental cache /// /// This uses "known paths" that have stayed relatively stable during cargo's lifetime. @@ -971,7 +1000,7 @@ impl BuildRequest { } /// libpatch-{time}.(so/dll/dylib) (next to the main exe) - pub fn patch_exe(&self, time_start: SystemTime) -> PathBuf { + pub(crate) fn patch_exe(&self, time_start: SystemTime) -> PathBuf { let path = self.main_exe().with_file_name(format!( "libpatch-{}", time_start.duration_since(UNIX_EPOCH).unwrap().as_millis(), @@ -994,7 +1023,7 @@ impl BuildRequest { /// Run our custom linker setup to generate a patch file in the right location async fn write_patch( &self, - ctx: &BuildContext, + _ctx: &BuildContext, aslr_reference: Option, time_start: SystemTime, ) -> Result<()> { @@ -1015,6 +1044,9 @@ impl BuildRequest { .map(|arg| PathBuf::from(arg)) .collect::>(); + // Our wasm approach is quite specific to wasm. We don't need to resolve any missing symbols + // there since wasm is relocatable, but there is considerable pre and post processing work to get it + // working. if self.platform != Platform::Web { let resolved_patch_bytes = subsecond_cli_support::resolve_undefined( &orig_exe, @@ -1050,9 +1082,6 @@ impl BuildRequest { let thin_args = self.thin_link_args(&args)?; - // let mut env_vars = vec![]; - // self.build_android_env(&mut env_vars, false)?; - // todo: we should throw out symbols that we don't need and/or assemble them manually // also we should make sure to propagate the right arguments (target, sysroot, etc) // @@ -1062,13 +1091,13 @@ impl BuildRequest { let res = Command::new(linker) .args(object_files.iter()) .args(thin_args) - .arg("-o") // is it "-o" everywhere? + .arg("-o") .arg(&self.patch_exe(time_start)) .output() .await?; - let errs = String::from_utf8_lossy(&res.stderr); - if !errs.is_empty() { + if !res.stderr.is_empty() { + let errs = String::from_utf8_lossy(&res.stderr); if !self.patch_exe(time_start).exists() { tracing::error!("Failed to generate patch: {}", errs.trim()); } else { @@ -1076,8 +1105,6 @@ impl BuildRequest { } } - if self.platform == Platform::Web {} - // // Clean up the temps manually // // todo: we might want to keep them around for debugging purposes // for file in object_files { @@ -1090,7 +1117,7 @@ impl BuildRequest { .iter() .position(|arg| *arg == "-o") .expect("failed to find -o"); - let link_file: PathBuf = args[link_orig + 1].clone().into(); + let link_file: PathBuf = args[link_orig + 1].into(); _ = std::fs::remove_file(&link_file); Ok(()) @@ -1105,8 +1132,7 @@ impl BuildRequest { tracing::trace!("original args:\n{}", original_args.join(" ")); match triple.operating_system { - // wasm32-unknown-unknown - // use wasm-ld (gnu-lld) + // wasm32-unknown-unknown -> use wasm-ld (gnu-lld) OperatingSystem::Unknown if self.platform == Platform::Web => { args.extend([ "--import-memory".to_string(), @@ -1212,48 +1238,54 @@ impl BuildRequest { args.push(vale); } - tracing::info!("final args:{:#?}", args); - Ok(args) } - #[tracing::instrument( - skip(self), - level = "trace", - fields(dx_src = ?TraceSrc::Build) - )] fn build_command(&self, ctx: &BuildContext) -> Result { match &ctx.mode { - BuildMode::Thin { direct_rustc, .. } => { + // We're assembling rustc directly, so we need to be *very* careful. Cargo sets rustc's + // env up very particularly, and we want to match it 1:1 but with some changes. + // + // To do this, we reset the env completely, and then pass every env var that the original + // rustc process had 1:1. + // + // We need to unset a few things, like the RUSTC wrappers and then our special env var + // indicating that dx itself is the compiler. If we forget to do this, then the compiler + // ends up doing some recursive nonsense and dx is trying to link instead of compiling. + // + // todo: maybe rustc needs to be found on the FS instead of using the one in the path? + BuildMode::Thin { rustc_args, .. } => { let mut cmd = Command::new("rustc"); + cmd.current_dir(self.workspace_dir()); cmd.env_clear(); - // let mut cmd = Command::new(direct_rustc.args[0].clone()); - cmd.args(direct_rustc.args[1..].iter()); - cmd.envs(direct_rustc.envs.iter()); - // cmd.envs(direct_rustc.envs.iter().filter(|(k, v)| { - // // (k.as_str() != "_") - // (k.as_str() != "RUSTC_WORKSPACE_WRAPPER") - // || (k.as_str() != "RUSTC_WRAPPER") - // || (k.as_str() != "DX_RUSTC") - // })); + cmd.args(rustc_args.args[1..].iter()); + cmd.envs(rustc_args.envs.iter()); cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); cmd.env_remove("RUSTC_WRAPPER"); - cmd.env_remove("DX_RUSTC"); - cmd.current_dir(self.workspace_dir()); + cmd.env_remove(DX_RUSTC_WRAPPER_ENV_VAR); + cmd.envs(self.env_vars(ctx)?); cmd.arg(format!( "-Clinker={}", dunce::canonicalize(std::env::current_exe().unwrap()) .unwrap() .display() )); - cmd.envs(self.env_vars(ctx)?); - tracing::trace!("Using rustc wrapper args: {:#?}", cmd); Ok(cmd) } - // Out of caution, use cargo rustc instead of dx as a fallback + // For Base and Fat builds, we use a regular cargo setup, but we might need to intercept + // rustc itself in case we're hot-patching and need a reliable rustc environment to + // continuously recompile the top-level crate with. + // + // In the future, when we support hot-patching *all* workspace crates, we will need to + // make use of the RUSTC_WORKSPACE_WRAPPER environment variable instead of RUSTC_WRAPPER + // and then keep track of env and args on a per-crate basis. + // + // We've also had a number of issues with incorrect canonicalization when passing paths + // through envs on windows, hence the frequent use of dunce::canonicalize. _ => { let mut cmd = Command::new("cargo"); + cmd.arg("rustc") .current_dir(self.crate_dir()) .arg("--message-format") @@ -1263,8 +1295,11 @@ impl BuildRequest { if ctx.mode == BuildMode::Fat { cmd.env( - crate::rustcwrapper::RUSTC_WRAPPER_ENV_VAR, - self.rustc_wrapper_args_file(), + DX_RUSTC_WRAPPER_ENV_VAR, + dunce::canonicalize(self.rustc_wrapper_args_file.path()) + .unwrap() + .display() + .to_string(), ); cmd.env( "RUSTC_WRAPPER", @@ -1309,9 +1344,9 @@ impl BuildRequest { cargo_args.push(self.cargo_package.clone()); match self.executable_type() { - krates::cm::TargetKind::Bin => cargo_args.push("--bin".to_string()), - krates::cm::TargetKind::Lib => cargo_args.push("--lib".to_string()), - krates::cm::TargetKind::Example => cargo_args.push("--example".to_string()), + TargetKind::Bin => cargo_args.push("--bin".to_string()), + TargetKind::Lib => cargo_args.push("--lib".to_string()), + TargetKind::Example => cargo_args.push("--example".to_string()), _ => {} }; @@ -1401,61 +1436,9 @@ impl BuildRequest { } } - // pub(crate) fn build_arguments(&self) -> Vec { - // let mut cargo_args = Vec::new(); - - // // Set the target, profile and features that vary between the app and server builds - // if self.build.platform() == Platform::Server { - // cargo_args.push("--profile".to_string()); - // match self.build.release { - // true => cargo_args.push("release".to_string()), - // false => cargo_args.push(self.build.server_profile.to_string()), - // }; - - // // If the user provided a server target, use it, otherwise use the default host target. - // if let Some(target) = self.build.target_args.server_target.as_deref() { - // cargo_args.push("--target".to_string()); - // cargo_args.push(target.to_string()); - // } - // } else { - // // Add required profile flags. --release overrides any custom profiles. - // let custom_profile = &self.build.profile.as_ref(); - // if custom_profile.is_some() || self.build.release { - // cargo_args.push("--profile".to_string()); - // match self.build.release { - // true => cargo_args.push("release".to_string()), - // false => { - // cargo_args.push( - // custom_profile - // .expect("custom_profile should have been checked by is_some") - // .to_string(), - // ); - // } - // }; - // } - // } - cargo_args } - pub(crate) fn all_target_features(&self) -> Vec { - let mut features = self.features.clone(); - - if !self.no_default_features { - features.extend( - self.package() - .features - .get("default") - .cloned() - .unwrap_or_default(), - ); - } - - features.dedup(); - - features - } - /// Try to get the unit graph for the crate. This is a nightly only feature which may not be available with the current version of rustc the user has installed. async fn get_unit_count(&self, ctx: &BuildContext) -> crate::Result { #[derive(Debug, Deserialize)] @@ -1508,88 +1491,48 @@ impl BuildRequest { fn env_vars(&self, ctx: &BuildContext) -> Result> { let mut env_vars = vec![]; - let mut custom_linker = None; - - // Make sure to set all the crazy android flags + // Make sure to set all the crazy android flags. Cross-compiling is hard, man. if self.platform == Platform::Android { - let linker = self.build_android_env(&mut env_vars, true)?; - - // 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)); - - // Also make sure to set the linker - custom_linker = Some(linker); + self.build_android_env(&mut env_vars, true)?; }; - match &ctx.mode { - // We don't usually employ a custom linker for fat/base builds unless it's android - // This might change in the future for "zero-linking" - BuildMode::Base | BuildMode::Fat => { - if let Some(linker) = custom_linker { - tracing::info!("Using custom linker for base link: {linker:?}"); - env_vars.push(( - LinkAction::ENV_VAR_NAME, - LinkAction::BaseLink { - linker, - extra_flags: vec![], - } - .to_json(), - )); + // If we're either zero-linking or using a custom linker, make `dx` itself do the linking. + if matches!(ctx.mode, BuildMode::Fat | BuildMode::Thin { .. }) + | self.custom_linker.is_some() + { + env_vars.push(( + LinkAction::ENV_VAR_NAME, + LinkAction { + linker: self.custom_linker.clone(), + link_err_file: self.link_err_file.path().to_path_buf(), + link_args_file: self.link_args_file.path().to_path_buf(), + triple: self.triple.clone(), } - } - - // We use a custom linker here (dx) but it doesn't actually do anything - BuildMode::Thin { .. } => { - std::fs::create_dir_all(self.link_args_file().parent().unwrap()); - env_vars.push(( - LinkAction::ENV_VAR_NAME, - LinkAction::ThinLink { - triple: self.triple.clone(), - save_link_args: self.link_args_file(), - } - .to_json(), - )) - } + .to_json(), + )); } 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 - // if self.release { - // if let Some(base_path) = &self.config.web.app.base_path { - // env_vars.push((ASSET_ROOT_ENV, base_path.clone())); - // } - // env_vars.push((APP_TITLE_ENV, self.config.web.app.title.clone())); - // } + // 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 { + if let Some(base_path) = &self.config.web.app.base_path { + env_vars.push((ASSET_ROOT_ENV, base_path.clone())); + } + env_vars.push((APP_TITLE_ENV, self.config.web.app.title.clone())); + } Ok(env_vars) } - pub fn build_android_env( + fn build_android_env( &self, env_vars: &mut Vec<(&str, String)>, rustf_flags: bool, - ) -> Result { + ) -> Result<()> { let tools = crate::build::android_tools().context("Could not determine android tools")?; let linker = tools.android_cc(&self.triple); let min_sdk_version = tools.min_sdk_version(); @@ -1643,7 +1586,46 @@ impl BuildRequest { rust_flags })); } - Ok(linker) + + // 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)); + + Ok(()) + } + + pub(crate) fn all_target_features(&self) -> Vec { + let mut features = self.features.clone(); + + if !self.no_default_features { + features.extend( + self.package() + .features + .get("default") + .cloned() + .unwrap_or_default(), + ); + } + + features.dedup(); + + features } /// returns the path to root build folder. This will be our working directory for the build. @@ -1678,11 +1660,11 @@ impl BuildRequest { } } - pub(crate) fn platform_dir(&self) -> PathBuf { + fn platform_dir(&self) -> PathBuf { self.build_dir(self.platform, self.release) } - pub fn platform_exe_name(&self) -> String { + fn platform_exe_name(&self) -> String { use convert_case::{Case, Casing}; match self.platform { Platform::MacOS => self.executable_name().to_string(), @@ -1925,25 +1907,6 @@ impl BuildRequest { .map(|dir| self.crate_dir().join(dir)) } - /// Get the list of files in the "legacy" asset directory - pub(crate) fn legacy_asset_dir_files(&self) -> Vec { - let mut files = vec![]; - - let Some(legacy_asset_dir) = self.legacy_asset_dir() else { - return files; - }; - - let Ok(read_dir) = legacy_asset_dir.read_dir() else { - return files; - }; - - for entry in read_dir.flatten() { - files.push(entry.path()); - } - - files - } - /// Get the directory where this app can write to for this session that's guaranteed to be stable /// for the same app. This is useful for emitting state like window position and size. /// @@ -2017,11 +1980,6 @@ impl BuildRequest { .to_path_buf() } - /// Get the main source file of the target - pub(crate) fn main_source_file(&self) -> PathBuf { - self.crate_target.src_path.as_std_path().to_path_buf() - } - /// Get the package we are currently in pub(crate) fn package(&self) -> &krates::cm::Package { &self.workspace.krates[self.crate_package] @@ -2033,7 +1991,7 @@ impl BuildRequest { } /// Get the type of executable we are compiling - pub(crate) fn executable_type(&self) -> krates::cm::TargetKind { + pub(crate) fn executable_type(&self) -> TargetKind { self.crate_target.kind[0].clone() } @@ -2152,12 +2110,6 @@ impl BuildRequest { }) } - /// Check if assets should be pre_compressed. This will only be true in release mode if the user - /// has enabled pre_compress in the web config. - pub(crate) fn should_pre_compress_web_assets(&self, release: bool) -> bool { - self.config.web.pre_compress && release - } - // The `opt-level=1` increases build times, but can noticeably decrease time // between saving changes and being able to interact with an app (for wasm/web). The "overall" // time difference (between having and not having the optimization) can be @@ -2207,7 +2159,7 @@ impl BuildRequest { } /// Return the version of the wasm-bindgen crate if it exists - pub fn wasm_bindgen_version(&self) -> Option { + pub(crate) fn wasm_bindgen_version(&self) -> Option { self.workspace .krates .krates_by_name("wasm-bindgen") @@ -2351,7 +2303,7 @@ impl BuildRequest { /// The item that we'll try to run directly if we need to. /// /// todo(jon): we should name the app properly instead of making up the exe name. It's kinda okay for dev mode, but def not okay for prod - pub fn main_exe(&self) -> PathBuf { + pub(crate) fn main_exe(&self) -> PathBuf { self.exe_dir().join(self.platform_exe_name()) } @@ -2385,7 +2337,7 @@ impl BuildRequest { /// ``` /// dx serve app --features "fullstack" /// ``` - pub fn fullstack_feature_enabled(&self) -> bool { + pub(crate) fn fullstack_feature_enabled(&self) -> bool { let dioxus_dep = self .package() .dependencies @@ -2408,27 +2360,6 @@ impl BuildRequest { false } - // /// We always put the server in the `web` folder! - // /// Only the `web` target will generate a `public` folder though - // async fn write_server_executable(&self) -> Result<()> { - // if let Some(server) = &self.server { - // let to = self - // .server_exe() - // .expect("server should be set if we're building a server"); - - // std::fs::create_dir_all(self.server_exe().unwrap().parent().unwrap())?; - - // tracing::debug!("Copying server executable to: {to:?} {server:#?}"); - - // // Remove the old server executable if it exists, since copying might corrupt it :( - // // todo(jon): do this in more places, I think - // _ = std::fs::remove_file(&to); - // std::fs::copy(&server.exe, to)?; - // } - - // Ok(()) - // } - /// todo(jon): use handlebars templates instead of these prebaked templates async fn write_metadata(&self) -> Result<()> { // write the Info.plist file @@ -2468,7 +2399,7 @@ impl BuildRequest { } /// Run the optimizers, obfuscators, minimizers, signers, etc - pub(crate) async fn optimize(&self, ctx: &BuildContext) -> Result<()> { + async fn optimize(&self, ctx: &BuildContext) -> Result<()> { match self.platform { Platform::Web => { // Compress the asset dir @@ -2495,21 +2426,11 @@ impl BuildRequest { Ok(()) } - // pub(crate) fn server_exe(&self) -> Option { - // if let Some(_server) = &self.server { - // let mut path = self.build_dir(Platform::Server, self.release); - - // if cfg!(windows) { - // path.push("server.exe"); - // } else { - // path.push("server"); - // } - - // return Some(path); - // } - - // None - // } + /// Check if assets should be pre_compressed. This will only be true in release mode if the user + /// has enabled pre_compress in the web config. + fn should_pre_compress_web_assets(&self, release: bool) -> bool { + self.config.web.pre_compress && release + } /// Bundle the web app /// - Run wasm-bindgen @@ -2528,7 +2449,7 @@ impl BuildRequest { // Locate the output of the build files and the bindgen output // We'll fill these in a second if they don't already exist let bindgen_outdir = self.wasm_bindgen_out_dir(); - let prebindgen = exe.clone(); + let prebindgen = exe; let post_bindgen_wasm = self.wasm_bindgen_wasm_output_file(); let should_bundle_split: bool = self.wasm_split; let rustc_exe = exe.with_extension("wasm"); @@ -2836,7 +2757,7 @@ impl BuildRequest { /// - extra scaffolding /// /// It's not guaranteed that they're different from any other folder - pub fn prepare_build_dir(&self) -> Result<()> { + pub(crate) fn prepare_build_dir(&self) -> Result<()> { use once_cell::sync::OnceCell; use std::fs::{create_dir_all, remove_dir_all}; @@ -2872,7 +2793,7 @@ impl BuildRequest { Ok(()) } - pub fn asset_dir(&self) -> PathBuf { + pub(crate) fn asset_dir(&self) -> PathBuf { match self.platform { Platform::MacOS => self .root_dir() @@ -2897,14 +2818,6 @@ impl BuildRequest { } } - pub fn incremental_cache_dir(&self) -> PathBuf { - self.platform_dir().join("incremental-cache") - } - - pub fn link_args_file(&self) -> PathBuf { - self.incremental_cache_dir().join("link_args.txt") - } - /// The directory in which we'll put the main exe /// /// Mac, Android, Web are a little weird @@ -2915,7 +2828,7 @@ impl BuildRequest { /// I think all others are just in the root folder /// /// todo(jon): investigate if we need to put .wasm in `wasm`. It kinda leaks implementation details, which ideally we don't want to do. - pub fn exe_dir(&self) -> PathBuf { + fn exe_dir(&self) -> PathBuf { match self.platform { Platform::MacOS => self.root_dir().join("Contents").join("MacOS"), Platform::Web => self.root_dir().join("wasm"), @@ -2939,35 +2852,43 @@ impl BuildRequest { } /// Get the path to the wasm bindgen temporary output folder - pub fn wasm_bindgen_out_dir(&self) -> PathBuf { + fn wasm_bindgen_out_dir(&self) -> PathBuf { self.root_dir().join("wasm") } /// Get the path to the wasm bindgen javascript output file - pub fn wasm_bindgen_js_output_file(&self) -> PathBuf { + pub(crate) fn wasm_bindgen_js_output_file(&self) -> PathBuf { self.wasm_bindgen_out_dir() .join(self.executable_name()) .with_extension("js") } /// Get the path to the wasm bindgen wasm output file - pub fn wasm_bindgen_wasm_output_file(&self) -> PathBuf { + pub(crate) fn wasm_bindgen_wasm_output_file(&self) -> PathBuf { self.wasm_bindgen_out_dir() .join(format!("{}_bg", self.executable_name())) .with_extension("wasm") } /// Get the path to the asset optimizer version file - pub fn asset_optimizer_version_file(&self) -> PathBuf { + pub(crate) fn asset_optimizer_version_file(&self) -> PathBuf { self.platform_dir().join(".cli-version") } - pub fn flush_session_cache(&self) { + fn flush_session_cache(&self) { let cache_dir = self.session_cache_dir(); _ = std::fs::remove_dir_all(&cache_dir); _ = std::fs::create_dir_all(&cache_dir); } + pub(crate) fn incremental_cache_dir(&self) -> PathBuf { + self.platform_dir().join("incremental-cache") + } + + pub(crate) fn link_args_file(&self) -> PathBuf { + self.incremental_cache_dir().join("link_args.txt") + } + /// Check for tooling that might be required for this build. /// /// This should generally be only called on the first build since it takes time to verify the tooling @@ -3091,11 +3012,16 @@ impl BuildRequest { Ok(()) } - /// "touch" the binary main file to bust the fingerprint, forcing rustc to recompile it. + /// update the mtime of the "main" file to bust the fingerprint, forcing rustc to recompile it. /// /// This prevents rustc from using the cached version of the binary, which can cause issues /// with our hotpatching setup since it uses linker interception. - pub fn bust_fingerprint(&self, ctx: &BuildContext) -> Result<()> { + /// + /// This is sadly a hack. I think there might be other ways of busting the fingerprint (rustc wrapper?) + /// but that would require relying on cargo internals. + /// + /// This might stop working if/when cargo stabilizies contents-based fingerprinting. + fn bust_fingerprint(&self, ctx: &BuildContext) -> Result<()> { if !matches!(ctx.mode, BuildMode::Thin { .. }) { std::fs::File::open(&self.crate_target.src_path)?.set_modified(SystemTime::now())?; } diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 1b484acd54..756ff3ed47 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,5 +1,5 @@ -use crate::Platform; use crate::{cli::*, AppBuilder, BuildRequest}; +use crate::{BuildMode, Platform}; use target_lexicon::Triple; /// Build the Rust Dioxus app and all of its assets. @@ -108,7 +108,9 @@ impl BuildArgs { .await .context("Failed to load Dioxus workspace")?; - AppBuilder::start(&build)?.finish_build().await?; + AppBuilder::start(&build, BuildMode::Base)? + .finish_build() + .await?; tracing::info!(path = ?build.root_dir(), "Build completed successfully! 🚀"); diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index e69242b449..ed6dbb2dda 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -1,4 +1,4 @@ -use crate::{AppBuilder, BuildArgs, BuildRequest, Platform}; +use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform}; use anyhow::{anyhow, Context}; use dioxus_cli_config::{server_ip, server_port}; use futures_util::stream::FuturesUnordered; @@ -78,7 +78,9 @@ impl Bundle { tracing::info!("Building app..."); - let bundle = AppBuilder::start(&build)?.finish_build().await?; + let bundle = AppBuilder::start(&build, BuildMode::Base)? + .finish_build() + .await?; // If we're building for iOS, we need to bundle the iOS bundle if build.platform == Platform::Ios && self.package_types.is_none() { diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index f144a4e67f..140fa6a600 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -1,19 +1,36 @@ -use crate::{Platform, Result}; +use crate::Result; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use target_lexicon::Triple; -use tokio::process::Command; +/// `dx` can act as a linker in a few scenarios. Note that we don't *actually* implement the linker logic, +/// instead just proxying to a specified linker (or not linking at all!). +/// +/// This comes in two flavors: +/// -------------------------- +/// - `BaseLink`: We are linking dependencies and want to dynamically select the linker from the environment. +/// This is mostly implemented for Android where the linker is selected in part by the +/// device connected over ADB which can not be determined by .cargo/Config.toml. +/// We implemented this because previous setups like cargo mobile required a hard-coded +/// linker path in your project which does not work in team-based setups. +/// +/// - `NoLink`: We are not linking at all, and instead deferring our linking to the driving process, +/// usually being `dx` itself. In this case, we are just writing the linker args to a file +/// and then outputting a dummy object file to satisfy the linker. This is generally used +/// by the binary patching engine since we need to actually do "real linker logic" like +/// traversing object files and satisifying missing symbols. That process is *much* easier +/// to do in the driving host procss when we have all the information available. Unfortuantely, +/// rustc doesn't provide a "real" way of granularly stepping through the compile process +/// so this is basically a hack. +/// +/// We use "BaseLink" when a linker is specified, and "NoLink" when it is not. Both generate a resulting +/// object file. #[derive(Debug, Serialize, Deserialize)] -pub enum LinkAction { - BaseLink { - linker: PathBuf, - extra_flags: Vec, - }, - ThinLink { - save_link_args: PathBuf, - triple: Triple, - }, +pub struct LinkAction { + pub linker: Option, + pub triple: Triple, + pub link_args_file: PathBuf, + pub link_err_file: PathBuf, } impl LinkAction { @@ -39,117 +56,82 @@ impl LinkAction { pub(crate) async fn run(self) -> Result<()> { let args = std::env::args().collect::>(); - match self { - // Run the system linker but (maybe) keep any unused sections. - LinkAction::BaseLink { - linker, - extra_flags, - } => { - let mut cmd = std::process::Command::new(linker); - cmd.args(args.iter().skip(1)); - cmd.args(extra_flags); - let res = cmd.output().expect("Failed to run android linker"); - - let err = String::from_utf8_lossy(&res.stderr); - std::fs::write( - "/Users/jonkelley/Development/dioxus/packages/subsecond/data/link-err.txt", - format!("err: {err}"), - ) - .unwrap(); - - // Make sure we *don't* dead-strip the binary so every library symbol still exists. - // This is required for thin linking to work correctly. - // let args = args.into_iter().skip(1).collect::>(); - // let res = Command::new(linker).args(args).output().await?; - // let err = String::from_utf8_lossy(&res.stderr); - - // .filter(|arg| arg != "-Wl,-dead_strip" && !strip) - - // this is ld64 only, we need --whole-archive for gnu/ld - // args.push("-Wl,-all_load".to_string()); - - // // Persist the cache of incremental files - // cache_incrementals( - // &incremental_dir.join("old"), - // &incremental_dir.join("new"), - // args.iter() - // .filter(|arg| arg.ends_with(".o")) - // .collect::>() - // .as_ref(), - // ); - - // Run ld with the args + // Write the linker args to a file for the main process to read + // todo: we might need to encode these as escaped shell words in case newlines are passed + std::fs::write(self.link_args_file, args.join("\n"))?; + + // If there's a linker specified, we use that. Otherwise, we write a dummy object file to satisfy + // any post-processing steps that rustc does. + match self.linker { + Some(linker) => { + let res = std::process::Command::new(linker) + .args(args.iter().skip(1)) + .output() + .expect("Failed to run android linker"); + + if !res.stderr.is_empty() { + _ = std::fs::write( + self.link_err_file, + String::from_utf8_lossy(&res.stderr).as_bytes(), + ) + .unwrap(); + } } - - // Run the linker but without rlibs - LinkAction::ThinLink { - save_link_args, - triple, - } => { - // Write the linker args to a file for the main process to read - std::fs::write(save_link_args, args.join("\n"))?; - - // Extract the out + None => { + // Extract the out path - we're going to write a dummy object file to satisfy the linker let out = args.iter().position(|arg| arg == "-o").unwrap(); let out_file: PathBuf = args[out + 1].clone().into(); + // This creates an object file that satisfies rust's use of llvm-objcopy + // + // I'd rather we *not* do this and instead generate a truly linked file (and then delete it) but + // this at least lets us delay linking until the host compiler is ready. + // + // This is because our host compiler is a stateful server and not a stateless linker. + // + // todo(jon): do we use Triple::host or the target triple? I think I ran into issues + // using the target triple, hence the use of "host" but it might not even matter? + let triple = Triple::host(); + let format = match triple.binary_format { + target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf, + target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff, + target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO, + target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm, + target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff, + target_lexicon::BinaryFormat::Unknown => todo!(), + _ => todo!("Binary format not supported"), + }; + + let arch = match triple.architecture { + target_lexicon::Architecture::Wasm32 => object::Architecture::Wasm32, + target_lexicon::Architecture::Wasm64 => object::Architecture::Wasm64, + target_lexicon::Architecture::X86_64 => object::Architecture::X86_64, + target_lexicon::Architecture::Arm(_) => object::Architecture::Arm, + target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64, + target_lexicon::Architecture::LoongArch64 => object::Architecture::LoongArch64, + target_lexicon::Architecture::Unknown => object::Architecture::Unknown, + _ => todo!("Architecture not supported"), + }; + + let endian = match triple.endianness() { + Ok(target_lexicon::Endianness::Little) => object::Endianness::Little, + Ok(target_lexicon::Endianness::Big) => object::Endianness::Big, + Err(_) => todo!("Endianness not supported"), + }; + + let bytes = object::write::Object::new(format, arch, endian) + .write() + .unwrap(); + // Write a dummy object file to satisfy rust/linker since it'll run llvm-objcopy // ... I wish it *didn't* do that but I can't tell how to disable the linker without // using --emit=obj which is not exactly what we want since that will still pull in // the dependencies. std::fs::create_dir_all(out_file.parent().unwrap())?; - std::fs::write(out_file, make_dummy_object_file(triple))?; + std::fs::write(out_file, bytes)?; } } Ok(()) } } - -/// This creates an object file that satisfies rust's use of llvm-objcopy -/// -/// I'd rather we *not* do this and instead generate a truly linked file (and then delete it) but -/// this at least lets us delay linking until the host compiler is ready. -/// -/// This is because our host compiler is a stateful server and not a stateless linker. -fn make_dummy_object_file(triple: Triple) -> Vec { - let triple = Triple::host(); - - let format = match triple.binary_format { - target_lexicon::BinaryFormat::Elf => object::BinaryFormat::Elf, - target_lexicon::BinaryFormat::Coff => object::BinaryFormat::Coff, - target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO, - target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm, - target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff, - target_lexicon::BinaryFormat::Unknown => todo!(), - _ => todo!("Binary format not supported"), - }; - - let arch = match triple.architecture { - target_lexicon::Architecture::Wasm32 => object::Architecture::Wasm32, - target_lexicon::Architecture::Wasm64 => object::Architecture::Wasm64, - target_lexicon::Architecture::X86_64 => object::Architecture::X86_64, - target_lexicon::Architecture::Arm(_) => object::Architecture::Arm, - target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64, - target_lexicon::Architecture::LoongArch64 => object::Architecture::LoongArch64, - target_lexicon::Architecture::Unknown => object::Architecture::Unknown, - _ => todo!("Architecture not supported"), - }; - - let endian = match triple.endianness() { - Ok(target_lexicon::Endianness::Little) => object::Endianness::Little, - Ok(target_lexicon::Endianness::Big) => object::Endianness::Big, - Err(_) => todo!("Endianness not supported"), - }; - - object::write::Object::new(format, arch, endian) - .write() - .unwrap() -} - -#[test] -fn test_make_dummy_object_file() { - let triple: Triple = "wasm32-unknown-unknown".parse().unwrap(); - let obj = make_dummy_object_file(triple); - assert!(!obj.is_empty()); -} diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index ff261ed4b4..70fa6b517e 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{AppBuilder, BuildArgs, BuildRequest, Platform, Result}; +use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform, Result}; /// Run the project with the given arguments #[derive(Clone, Debug, Parser)] @@ -15,7 +15,7 @@ impl RunArgs { .await .context("error building project")?; - let mut builder = AppBuilder::start(&build)?; + let mut builder = AppBuilder::start(&build, BuildMode::Base)?; let artifacts = builder.finish_build().await?; let devserver_ip = "127.0.0.1:8081".parse().unwrap(); diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 7b4c721c0e..d508747929 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -34,13 +34,13 @@ pub(crate) use workspace::*; #[tokio::main] async fn main() { // The CLI uses dx as a rustcwrapper in some instances (like binary patching) - if rustcwrapper::is_rustc() { + if rustcwrapper::is_wrapping_rustc() { return rustcwrapper::run_rustc().await; } // If we're being ran as a linker (likely from ourselves), we want to act as a linker instead. - if let Some(link_action) = link::LinkAction::from_env() { - return link_action.run().await.unwrap(); + if let Some(link_args) = link::LinkAction::from_env() { + return link_args.run().await.unwrap(); } let args = TraceController::initialize(); diff --git a/packages/cli/src/rustcwrapper.rs b/packages/cli/src/rustcwrapper.rs index 43bf564e1a..4da0025374 100644 --- a/packages/cli/src/rustcwrapper.rs +++ b/packages/cli/src/rustcwrapper.rs @@ -1,17 +1,28 @@ -use std::{collections::HashMap, path::PathBuf, process::ExitCode}; - -use crate::{Platform, Result}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + env::{args, vars}, + path::PathBuf, +}; /// The environment variable indicating where the args file is located. /// /// When `dx-rustc` runs, it writes its arguments to this file. -pub const RUSTC_WRAPPER_ENV_VAR: &str = "DX_RUSTC"; +pub const DX_RUSTC_WRAPPER_ENV_VAR: &str = "DX_RUSTC"; -pub fn is_rustc() -> bool { - std::env::var(RUSTC_WRAPPER_ENV_VAR).is_ok() +/// Is `dx` being used as a rustc wrapper? +/// +/// This is primarily used to intercept cargo, enabling fast hot-patching by caching the environment +/// cargo setups up for the user's current project. +/// +/// In a differenet world we could simply rely on cargo printing link args and the rustc command, but +/// it doesn't seem to output that in a reliable, parseable, cross-platform format (ie using command +/// files on windows...), so we're forced to do this interception nonsense. +pub fn is_wrapping_rustc() -> bool { + std::env::var(DX_RUSTC_WRAPPER_ENV_VAR).is_ok() } -#[derive(Default, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RustcArgs { pub args: Vec, pub envs: HashMap, @@ -21,15 +32,13 @@ pub struct RustcArgs { /// /// https://doc.rust-lang.org/cargo/reference/config.html#buildrustc pub async fn run_rustc() { - let var_file: PathBuf = std::env::var(RUSTC_WRAPPER_ENV_VAR) + let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR) .expect("DX_RUSTC not set") .into(); let rustc_args = RustcArgs { - envs: std::env::vars() - .map(|(k, v)| (k, v)) - .collect::>(), - args: std::env::args().skip(1).collect::>(), + args: args().skip(1).collect::>(), + envs: vars().map(|(k, v)| (k, v)).collect::>(), }; std::fs::create_dir_all(var_file.parent().expect("Failed to get parent dir")) @@ -41,6 +50,10 @@ pub async fn run_rustc() { .expect("Failed to write rustc args to file"); // Run the actual rustc command + // We want all stdout/stderr to be inherited, so the running process can see the output + // + // Note that the args format we get from the wrapper includes the `rustc` command itself, so we + // need to skip that - we already skipped the first arg when we created the args struct. let mut cmd = std::process::Command::new("rustc"); cmd.args(rustc_args.args.iter().skip(1)); cmd.envs(rustc_args.envs); diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 0450681165..93d83da4b3 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -145,20 +145,16 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { } } BuildMode::Base | BuildMode::Fat => { - let handle = builder + builder .open( bundle, devserver.devserver_address(), devserver.proxied_server_address(), devserver.displayed_address(), + &mut devserver, ) .await .inspect_err(|e| tracing::error!("Failed to open app: {}", e)); - - // Update the screen + devserver with the new handle info - if handle.is_ok() { - devserver.send_reload_command().await - } } } } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 8775968b6c..d9dd5de23b 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -180,8 +180,13 @@ impl AppRunner { .then(|| get_available_port(devserver_bind_ip, None)) .flatten(); - let client = AppBuilder::start(&client).unwrap(); - let server = server.map(|server| AppBuilder::start(&server).unwrap()); + let build_mode = match args.hot_patch { + true => BuildMode::Fat, + false => BuildMode::Base, + }; + + let client = AppBuilder::start(&client, build_mode.clone()).unwrap(); + let server = server.map(|server| AppBuilder::start(&server, build_mode).unwrap()); tracing::debug!("Proxied port: {:?}", proxied_port); @@ -465,41 +470,39 @@ impl AppRunner { devserver_ip: SocketAddr, fullstack_address: Option, displayed_address: Option, + devserver: &mut WebServer, ) -> Result<()> { // Add some cute logging let time_taken = artifacts .time_end .duration_since(artifacts.time_start) .unwrap(); - if self.builds_opened == 0 { - tracing::info!( - "Build completed successfully in {:?}ms, launching app! 💫", - time_taken.as_millis() - ); - } else { - tracing::info!("Build completed in {:?}ms", time_taken.as_millis()); - } // Make sure to save artifacts... match artifacts.platform { Platform::Server => { if let Some(server) = self.server.as_mut() { server.artifacts = Some(artifacts.clone()); - } else { - tracing::warn!("Server build completed but no server runner was created"); } } - _ => { - self.client.artifacts = Some(artifacts.clone()); - } + _ => self.client.artifacts = Some(artifacts.clone()), } let should_open = self.client.stage == BuildStage::Success && (self.server.as_ref().map(|s| s.stage == BuildStage::Success)).unwrap_or(true); if should_open { + if self.builds_opened == 0 { + tracing::info!( + "Build completed successfully in {:?}ms, launching app! 💫", + time_taken.as_millis() + ); + } else { + tracing::info!("Build completed in {:?}ms", time_taken.as_millis()); + } + // Always open the server first after the client has been built - if let Some(server) = self.server.as_ref() { + if let Some(_server) = self.server.as_ref() { self.open_server(devserver_ip, fullstack_address, displayed_address) .await?; } @@ -519,6 +522,12 @@ impl AppRunner { .await?; self.builds_opened += 1; + + // Give a second for the server to boot + tokio::time::sleep(Duration::from_millis(300)).await; + + // Update the screen + devserver with the new handle info + devserver.send_reload_command().await } Ok(()) diff --git a/packages/depinfo/src/dx.d b/packages/depinfo/src/dx.d index 2b6ee90fb7..be32ed87c0 100644 --- a/packages/depinfo/src/dx.d +++ b/packages/depinfo/src/dx.d @@ -1 +1 @@ -/Users/jonkelley/Development/dioxus/target/debug/dx: /Users/jonkelley/Development/dioxus/packages/autofmt/README.md /Users/jonkelley/Development/dioxus/packages/autofmt/src/buffer.rs /Users/jonkelley/Development/dioxus/packages/autofmt/src/collect_macros.rs /Users/jonkelley/Development/dioxus/packages/autofmt/src/indent.rs /Users/jonkelley/Development/dioxus/packages/autofmt/src/lib.rs /Users/jonkelley/Development/dioxus/packages/autofmt/src/prettier_please.rs /Users/jonkelley/Development/dioxus/packages/autofmt/src/writer.rs /Users/jonkelley/Development/dioxus/packages/check/README.md /Users/jonkelley/Development/dioxus/packages/check/src/check.rs /Users/jonkelley/Development/dioxus/packages/check/src/issues.rs /Users/jonkelley/Development/dioxus/packages/check/src/lib.rs /Users/jonkelley/Development/dioxus/packages/check/src/metadata.rs /Users/jonkelley/Development/dioxus/packages/cli/README.md /Users/jonkelley/Development/dioxus/packages/cli/assets/android/MainActivity.kt.hbs /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/build.gradle.kts /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradle.properties /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradlew /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradlew.bat /Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/settings.gradle /Users/jonkelley/Development/dioxus/packages/cli/assets/dioxus.toml /Users/jonkelley/Development/dioxus/packages/cli/assets/ios/ios.plist.hbs /Users/jonkelley/Development/dioxus/packages/cli/assets/macos/mac.plist.hbs /Users/jonkelley/Development/dioxus/packages/cli/assets/web/index.html /Users/jonkelley/Development/dioxus/packages/cli/assets/web/loading.html /Users/jonkelley/Development/dioxus/packages/cli/assets/web/toast.html /Users/jonkelley/Development/dioxus/packages/cli/build.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/builder.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/bundle.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/mod.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/prerender.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/progress.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/request.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/templates.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/verify.rs /Users/jonkelley/Development/dioxus/packages/cli/src/build/web.rs /Users/jonkelley/Development/dioxus/packages/cli/src/bundle_utils.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/autoformat.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/build.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/bundle.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/check.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/clean.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/config.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/create.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/init.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/link.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/mod.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/run.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/serve.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/target.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/translate.rs /Users/jonkelley/Development/dioxus/packages/cli/src/cli/verbosity.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config/app.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config/bundle.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config/desktop.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config/dioxus_config.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config/serve.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config/web.rs /Users/jonkelley/Development/dioxus/packages/cli/src/config.rs /Users/jonkelley/Development/dioxus/packages/cli/src/dioxus_crate.rs /Users/jonkelley/Development/dioxus/packages/cli/src/dx_build_info.rs /Users/jonkelley/Development/dioxus/packages/cli/src/error.rs /Users/jonkelley/Development/dioxus/packages/cli/src/fastfs.rs /Users/jonkelley/Development/dioxus/packages/cli/src/filemap.rs /Users/jonkelley/Development/dioxus/packages/cli/src/logging.rs /Users/jonkelley/Development/dioxus/packages/cli/src/main.rs /Users/jonkelley/Development/dioxus/packages/cli/src/metadata.rs /Users/jonkelley/Development/dioxus/packages/cli/src/platform.rs /Users/jonkelley/Development/dioxus/packages/cli/src/rustc.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/ansi_buffer.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/detect.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/handle.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/mod.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/output.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/proxy.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/runner.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/server.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/update.rs /Users/jonkelley/Development/dioxus/packages/cli/src/serve/watcher.rs /Users/jonkelley/Development/dioxus/packages/cli/src/settings.rs /Users/jonkelley/Development/dioxus/packages/cli/src/wasm_bindgen.rs /Users/jonkelley/Development/dioxus/packages/cli-config/src/lib.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/css.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/file.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/folder.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/image/jpg.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/image/mod.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/image/png.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/js.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/json.rs /Users/jonkelley/Development/dioxus/packages/cli-opt/src/lib.rs /Users/jonkelley/Development/dioxus/packages/config-macro/README.md /Users/jonkelley/Development/dioxus/packages/config-macro/src/lib.rs /Users/jonkelley/Development/dioxus/packages/const-serialize/README.md /Users/jonkelley/Development/dioxus/packages/const-serialize/src/const_buffers.rs /Users/jonkelley/Development/dioxus/packages/const-serialize/src/const_vec.rs /Users/jonkelley/Development/dioxus/packages/const-serialize/src/lib.rs /Users/jonkelley/Development/dioxus/packages/const-serialize-macro/src/lib.rs /Users/jonkelley/Development/dioxus/packages/core/README.md /Users/jonkelley/Development/dioxus/packages/core/docs/common_spawn_errors.md /Users/jonkelley/Development/dioxus/packages/core/docs/reactivity.md /Users/jonkelley/Development/dioxus/packages/core/src/any_props.rs /Users/jonkelley/Development/dioxus/packages/core/src/arena.rs /Users/jonkelley/Development/dioxus/packages/core/src/diff/component.rs /Users/jonkelley/Development/dioxus/packages/core/src/diff/iterator.rs /Users/jonkelley/Development/dioxus/packages/core/src/diff/mod.rs /Users/jonkelley/Development/dioxus/packages/core/src/diff/node.rs /Users/jonkelley/Development/dioxus/packages/core/src/effect.rs /Users/jonkelley/Development/dioxus/packages/core/src/error_boundary.rs /Users/jonkelley/Development/dioxus/packages/core/src/events.rs /Users/jonkelley/Development/dioxus/packages/core/src/fragment.rs /Users/jonkelley/Development/dioxus/packages/core/src/generational_box.rs /Users/jonkelley/Development/dioxus/packages/core/src/global_context.rs /Users/jonkelley/Development/dioxus/packages/core/src/hotreload_utils.rs /Users/jonkelley/Development/dioxus/packages/core/src/launch.rs /Users/jonkelley/Development/dioxus/packages/core/src/lib.rs /Users/jonkelley/Development/dioxus/packages/core/src/mutations.rs /Users/jonkelley/Development/dioxus/packages/core/src/nodes.rs /Users/jonkelley/Development/dioxus/packages/core/src/properties.rs /Users/jonkelley/Development/dioxus/packages/core/src/reactive_context.rs /Users/jonkelley/Development/dioxus/packages/core/src/render_error.rs /Users/jonkelley/Development/dioxus/packages/core/src/root_wrapper.rs /Users/jonkelley/Development/dioxus/packages/core/src/runtime.rs /Users/jonkelley/Development/dioxus/packages/core/src/scheduler.rs /Users/jonkelley/Development/dioxus/packages/core/src/scope_arena.rs /Users/jonkelley/Development/dioxus/packages/core/src/scope_context.rs /Users/jonkelley/Development/dioxus/packages/core/src/scopes.rs /Users/jonkelley/Development/dioxus/packages/core/src/suspense/component.rs /Users/jonkelley/Development/dioxus/packages/core/src/suspense/mod.rs /Users/jonkelley/Development/dioxus/packages/core/src/tasks.rs /Users/jonkelley/Development/dioxus/packages/core/src/virtual_dom.rs /Users/jonkelley/Development/dioxus/packages/core-macro/README.md /Users/jonkelley/Development/dioxus/packages/core-macro/docs/component.md /Users/jonkelley/Development/dioxus/packages/core-macro/docs/props.md /Users/jonkelley/Development/dioxus/packages/core-macro/docs/rsx.md /Users/jonkelley/Development/dioxus/packages/core-macro/src/component.rs /Users/jonkelley/Development/dioxus/packages/core-macro/src/lib.rs /Users/jonkelley/Development/dioxus/packages/core-macro/src/props/mod.rs /Users/jonkelley/Development/dioxus/packages/core-macro/src/utils.rs /Users/jonkelley/Development/dioxus/packages/core-types/src/bubbles.rs /Users/jonkelley/Development/dioxus/packages/core-types/src/bundled.rs /Users/jonkelley/Development/dioxus/packages/core-types/src/formatter.rs /Users/jonkelley/Development/dioxus/packages/core-types/src/hr_context.rs /Users/jonkelley/Development/dioxus/packages/core-types/src/lib.rs /Users/jonkelley/Development/dioxus/packages/devtools/src/lib.rs /Users/jonkelley/Development/dioxus/packages/devtools-types/src/lib.rs /Users/jonkelley/Development/dioxus/packages/dioxus-lib/README.md /Users/jonkelley/Development/dioxus/packages/dioxus-lib/src/lib.rs /Users/jonkelley/Development/dioxus/packages/document/build.rs /Users/jonkelley/Development/dioxus/packages/document/docs/eval.md /Users/jonkelley/Development/dioxus/packages/document/docs/head.md /Users/jonkelley/Development/dioxus/packages/document/src/document.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/link.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/meta.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/mod.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/script.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/style.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/stylesheet.rs /Users/jonkelley/Development/dioxus/packages/document/src/elements/title.rs /Users/jonkelley/Development/dioxus/packages/document/src/error.rs /Users/jonkelley/Development/dioxus/packages/document/src/eval.rs /Users/jonkelley/Development/dioxus/packages/document/src/js/head.js /Users/jonkelley/Development/dioxus/packages/document/src/lib.rs /Users/jonkelley/Development/dioxus/packages/document/./src/ts/eval.ts /Users/jonkelley/Development/dioxus/packages/document/./src/ts/head.ts /Users/jonkelley/Development/dioxus/packages/dx-wire-format/src/lib.rs /Users/jonkelley/Development/dioxus/packages/fullstack/README.md /Users/jonkelley/Development/dioxus/packages/fullstack/src/document/mod.rs /Users/jonkelley/Development/dioxus/packages/fullstack/src/hooks/mod.rs /Users/jonkelley/Development/dioxus/packages/fullstack/src/hooks/server_cached.rs /Users/jonkelley/Development/dioxus/packages/fullstack/src/hooks/server_future.rs /Users/jonkelley/Development/dioxus/packages/fullstack/src/html_storage/mod.rs /Users/jonkelley/Development/dioxus/packages/fullstack/src/lib.rs /Users/jonkelley/Development/dioxus/packages/generational-box/README.md /Users/jonkelley/Development/dioxus/packages/generational-box/src/entry.rs /Users/jonkelley/Development/dioxus/packages/generational-box/src/error.rs /Users/jonkelley/Development/dioxus/packages/generational-box/src/lib.rs /Users/jonkelley/Development/dioxus/packages/generational-box/src/references.rs /Users/jonkelley/Development/dioxus/packages/generational-box/src/sync.rs /Users/jonkelley/Development/dioxus/packages/generational-box/src/unsync.rs /Users/jonkelley/Development/dioxus/packages/history/src/lib.rs /Users/jonkelley/Development/dioxus/packages/history/src/memory.rs /Users/jonkelley/Development/dioxus/packages/hooks/README.md /Users/jonkelley/Development/dioxus/packages/hooks/docs/derived_state.md /Users/jonkelley/Development/dioxus/packages/hooks/docs/moving_state_around.md /Users/jonkelley/Development/dioxus/packages/hooks/docs/rules_of_hooks.md /Users/jonkelley/Development/dioxus/packages/hooks/docs/side_effects.md /Users/jonkelley/Development/dioxus/packages/hooks/docs/use_resource.md /Users/jonkelley/Development/dioxus/packages/hooks/src/lib.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_callback.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_context.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_coroutine.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_effect.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_future.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_hook_did_run.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_memo.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_on_destroy.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_reactive.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_resource.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_root_context.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_set_compare.rs /Users/jonkelley/Development/dioxus/packages/hooks/src/use_signal.rs /Users/jonkelley/Development/dioxus/packages/html/README.md /Users/jonkelley/Development/dioxus/packages/html/docs/common_event_handler_errors.md /Users/jonkelley/Development/dioxus/packages/html/docs/event_handlers.md /Users/jonkelley/Development/dioxus/packages/html/src/attribute_groups.rs /Users/jonkelley/Development/dioxus/packages/html/src/elements.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/animation.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/clipboard.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/composition.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/drag.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/focus.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/form.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/image.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/keyboard.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/media.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/mod.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/mounted.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/mouse.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/pointer.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/resize.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/scroll.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/selection.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/toggle.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/touch.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/transition.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/visible.rs /Users/jonkelley/Development/dioxus/packages/html/src/events/wheel.rs /Users/jonkelley/Development/dioxus/packages/html/src/file_data.rs /Users/jonkelley/Development/dioxus/packages/html/src/geometry.rs /Users/jonkelley/Development/dioxus/packages/html/src/input_data.rs /Users/jonkelley/Development/dioxus/packages/html/src/lib.rs /Users/jonkelley/Development/dioxus/packages/html/src/point_interaction.rs /Users/jonkelley/Development/dioxus/packages/html/src/render_template.rs /Users/jonkelley/Development/dioxus/packages/html-internal-macro/src/lib.rs /Users/jonkelley/Development/dioxus/packages/lazy-js-bundle/src/lib.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis/README.md /Users/jonkelley/Development/dioxus/packages/manganis/manganis/src/hash.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis/src/lib.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis/src/macro_helpers.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/asset.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/css.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/folder.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/hash.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/images.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/js.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/lib.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/linker.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/options.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/README.md /Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/src/asset.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/src/lib.rs /Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/src/linker.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/assign_dyn_ids.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/attribute.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/component.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/diagnostics.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/element.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/expr_node.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/forloop.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/ifchain.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/ifmt.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/lib.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/literal.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/location.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/node.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/partial_closure.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/raw_expr.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/rsx_block.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/rsx_call.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/template_body.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/text_node.rs /Users/jonkelley/Development/dioxus/packages/rsx/src/util.rs /Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/collect.rs /Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/diff.rs /Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/extensions.rs /Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/last_build_state.rs /Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/lib.rs /Users/jonkelley/Development/dioxus/packages/rsx-rosetta/README.md /Users/jonkelley/Development/dioxus/packages/rsx-rosetta/src/lib.rs /Users/jonkelley/Development/dioxus/packages/server-macro/src/lib.rs /Users/jonkelley/Development/dioxus/packages/signals/README.md /Users/jonkelley/Development/dioxus/packages/signals/docs/hoist/error.rs /Users/jonkelley/Development/dioxus/packages/signals/docs/hoist/fixed_list.rs /Users/jonkelley/Development/dioxus/packages/signals/docs/memo.md /Users/jonkelley/Development/dioxus/packages/signals/docs/signals.md /Users/jonkelley/Development/dioxus/packages/signals/src/copy_value.rs /Users/jonkelley/Development/dioxus/packages/signals/src/global/memo.rs /Users/jonkelley/Development/dioxus/packages/signals/src/global/mod.rs /Users/jonkelley/Development/dioxus/packages/signals/src/global/signal.rs /Users/jonkelley/Development/dioxus/packages/signals/src/impls.rs /Users/jonkelley/Development/dioxus/packages/signals/src/lib.rs /Users/jonkelley/Development/dioxus/packages/signals/src/map.rs /Users/jonkelley/Development/dioxus/packages/signals/src/memo.rs /Users/jonkelley/Development/dioxus/packages/signals/src/props.rs /Users/jonkelley/Development/dioxus/packages/signals/src/read.rs /Users/jonkelley/Development/dioxus/packages/signals/src/read_only_signal.rs /Users/jonkelley/Development/dioxus/packages/signals/src/set_compare.rs /Users/jonkelley/Development/dioxus/packages/signals/src/signal.rs /Users/jonkelley/Development/dioxus/packages/signals/src/warnings.rs /Users/jonkelley/Development/dioxus/packages/signals/src/write.rs /Users/jonkelley/Development/dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs +/dioxus/target/debug/dx: /dioxus/packages/autofmt/README.md /dioxus/packages/autofmt/src/buffer.rs /dioxus/packages/autofmt/src/collect_macros.rs /dioxus/packages/autofmt/src/indent.rs /dioxus/packages/autofmt/src/lib.rs /dioxus/packages/autofmt/src/prettier_please.rs /dioxus/packages/autofmt/src/writer.rs /dioxus/packages/check/README.md /dioxus/packages/check/src/check.rs /dioxus/packages/check/src/issues.rs /dioxus/packages/check/src/lib.rs /dioxus/packages/check/src/metadata.rs /dioxus/packages/cli/README.md /dioxus/packages/cli/assets/android/MainActivity.kt.hbs /dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs /dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro /dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs /dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp /dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml /dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs /dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml /dioxus/packages/cli/assets/android/gen/build.gradle.kts /dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar /dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties /dioxus/packages/cli/assets/android/gen/gradle.properties /dioxus/packages/cli/assets/android/gen/gradlew /dioxus/packages/cli/assets/android/gen/gradlew.bat /dioxus/packages/cli/assets/android/gen/settings.gradle /dioxus/packages/cli/assets/dioxus.toml /dioxus/packages/cli/assets/ios/ios.plist.hbs /dioxus/packages/cli/assets/macos/mac.plist.hbs /dioxus/packages/cli/assets/web/index.html /dioxus/packages/cli/assets/web/loading.html /dioxus/packages/cli/assets/web/toast.html /dioxus/packages/cli/build.rs /dioxus/packages/cli/src/build/builder.rs /dioxus/packages/cli/src/build/bundle.rs /dioxus/packages/cli/src/build/mod.rs /dioxus/packages/cli/src/build/prerender.rs /dioxus/packages/cli/src/build/progress.rs /dioxus/packages/cli/src/build/request.rs /dioxus/packages/cli/src/build/templates.rs /dioxus/packages/cli/src/build/verify.rs /dioxus/packages/cli/src/build/web.rs /dioxus/packages/cli/src/bundle_utils.rs /dioxus/packages/cli/src/cli/autoformat.rs /dioxus/packages/cli/src/cli/build.rs /dioxus/packages/cli/src/cli/bundle.rs /dioxus/packages/cli/src/cli/check.rs /dioxus/packages/cli/src/cli/clean.rs /dioxus/packages/cli/src/cli/config.rs /dioxus/packages/cli/src/cli/create.rs /dioxus/packages/cli/src/cli/init.rs /dioxus/packages/cli/src/cli/link.rs /dioxus/packages/cli/src/cli/mod.rs /dioxus/packages/cli/src/cli/run.rs /dioxus/packages/cli/src/cli/serve.rs /dioxus/packages/cli/src/cli/target.rs /dioxus/packages/cli/src/cli/translate.rs /dioxus/packages/cli/src/cli/verbosity.rs /dioxus/packages/cli/src/config/app.rs /dioxus/packages/cli/src/config/bundle.rs /dioxus/packages/cli/src/config/desktop.rs /dioxus/packages/cli/src/config/dioxus_config.rs /dioxus/packages/cli/src/config/serve.rs /dioxus/packages/cli/src/config/web.rs /dioxus/packages/cli/src/config.rs /dioxus/packages/cli/src/dioxus_crate.rs /dioxus/packages/cli/src/dx_build_info.rs /dioxus/packages/cli/src/error.rs /dioxus/packages/cli/src/fastfs.rs /dioxus/packages/cli/src/filemap.rs /dioxus/packages/cli/src/logging.rs /dioxus/packages/cli/src/main.rs /dioxus/packages/cli/src/metadata.rs /dioxus/packages/cli/src/platform.rs /dioxus/packages/cli/src/rustc.rs /dioxus/packages/cli/src/serve/ansi_buffer.rs /dioxus/packages/cli/src/serve/detect.rs /dioxus/packages/cli/src/serve/handle.rs /dioxus/packages/cli/src/serve/mod.rs /dioxus/packages/cli/src/serve/output.rs /dioxus/packages/cli/src/serve/proxy.rs /dioxus/packages/cli/src/serve/runner.rs /dioxus/packages/cli/src/serve/server.rs /dioxus/packages/cli/src/serve/update.rs /dioxus/packages/cli/src/serve/watcher.rs /dioxus/packages/cli/src/settings.rs /dioxus/packages/cli/src/wasm_bindgen.rs /dioxus/packages/cli-config/src/lib.rs /dioxus/packages/cli-opt/src/css.rs /dioxus/packages/cli-opt/src/file.rs /dioxus/packages/cli-opt/src/folder.rs /dioxus/packages/cli-opt/src/image/jpg.rs /dioxus/packages/cli-opt/src/image/mod.rs /dioxus/packages/cli-opt/src/image/png.rs /dioxus/packages/cli-opt/src/js.rs /dioxus/packages/cli-opt/src/json.rs /dioxus/packages/cli-opt/src/lib.rs /dioxus/packages/config-macro/README.md /dioxus/packages/config-macro/src/lib.rs /dioxus/packages/const-serialize/README.md /dioxus/packages/const-serialize/src/const_buffers.rs /dioxus/packages/const-serialize/src/const_vec.rs /dioxus/packages/const-serialize/src/lib.rs /dioxus/packages/const-serialize-macro/src/lib.rs /dioxus/packages/core/README.md /dioxus/packages/core/docs/common_spawn_errors.md /dioxus/packages/core/docs/reactivity.md /dioxus/packages/core/src/any_props.rs /dioxus/packages/core/src/arena.rs /dioxus/packages/core/src/diff/component.rs /dioxus/packages/core/src/diff/iterator.rs /dioxus/packages/core/src/diff/mod.rs /dioxus/packages/core/src/diff/node.rs /dioxus/packages/core/src/effect.rs /dioxus/packages/core/src/error_boundary.rs /dioxus/packages/core/src/events.rs /dioxus/packages/core/src/fragment.rs /dioxus/packages/core/src/generational_box.rs /dioxus/packages/core/src/global_context.rs /dioxus/packages/core/src/hotreload_utils.rs /dioxus/packages/core/src/launch.rs /dioxus/packages/core/src/lib.rs /dioxus/packages/core/src/mutations.rs /dioxus/packages/core/src/nodes.rs /dioxus/packages/core/src/properties.rs /dioxus/packages/core/src/reactive_context.rs /dioxus/packages/core/src/render_error.rs /dioxus/packages/core/src/root_wrapper.rs /dioxus/packages/core/src/runtime.rs /dioxus/packages/core/src/scheduler.rs /dioxus/packages/core/src/scope_arena.rs /dioxus/packages/core/src/scope_context.rs /dioxus/packages/core/src/scopes.rs /dioxus/packages/core/src/suspense/component.rs /dioxus/packages/core/src/suspense/mod.rs /dioxus/packages/core/src/tasks.rs /dioxus/packages/core/src/virtual_dom.rs /dioxus/packages/core-macro/README.md /dioxus/packages/core-macro/docs/component.md /dioxus/packages/core-macro/docs/props.md /dioxus/packages/core-macro/docs/rsx.md /dioxus/packages/core-macro/src/component.rs /dioxus/packages/core-macro/src/lib.rs /dioxus/packages/core-macro/src/props/mod.rs /dioxus/packages/core-macro/src/utils.rs /dioxus/packages/core-types/src/bubbles.rs /dioxus/packages/core-types/src/bundled.rs /dioxus/packages/core-types/src/formatter.rs /dioxus/packages/core-types/src/hr_context.rs /dioxus/packages/core-types/src/lib.rs /dioxus/packages/devtools/src/lib.rs /dioxus/packages/devtools-types/src/lib.rs /dioxus/packages/dioxus-lib/README.md /dioxus/packages/dioxus-lib/src/lib.rs /dioxus/packages/document/build.rs /dioxus/packages/document/docs/eval.md /dioxus/packages/document/docs/head.md /dioxus/packages/document/src/document.rs /dioxus/packages/document/src/elements/link.rs /dioxus/packages/document/src/elements/meta.rs /dioxus/packages/document/src/elements/mod.rs /dioxus/packages/document/src/elements/script.rs /dioxus/packages/document/src/elements/style.rs /dioxus/packages/document/src/elements/stylesheet.rs /dioxus/packages/document/src/elements/title.rs /dioxus/packages/document/src/error.rs /dioxus/packages/document/src/eval.rs /dioxus/packages/document/src/js/head.js /dioxus/packages/document/src/lib.rs /dioxus/packages/document/./src/ts/eval.ts /dioxus/packages/document/./src/ts/head.ts /dioxus/packages/dx-wire-format/src/lib.rs /dioxus/packages/fullstack/README.md /dioxus/packages/fullstack/src/document/mod.rs /dioxus/packages/fullstack/src/hooks/mod.rs /dioxus/packages/fullstack/src/hooks/server_cached.rs /dioxus/packages/fullstack/src/hooks/server_future.rs /dioxus/packages/fullstack/src/html_storage/mod.rs /dioxus/packages/fullstack/src/lib.rs /dioxus/packages/generational-box/README.md /dioxus/packages/generational-box/src/entry.rs /dioxus/packages/generational-box/src/error.rs /dioxus/packages/generational-box/src/lib.rs /dioxus/packages/generational-box/src/references.rs /dioxus/packages/generational-box/src/sync.rs /dioxus/packages/generational-box/src/unsync.rs /dioxus/packages/history/src/lib.rs /dioxus/packages/history/src/memory.rs /dioxus/packages/hooks/README.md /dioxus/packages/hooks/docs/derived_state.md /dioxus/packages/hooks/docs/moving_state_around.md /dioxus/packages/hooks/docs/rules_of_hooks.md /dioxus/packages/hooks/docs/side_effects.md /dioxus/packages/hooks/docs/use_resource.md /dioxus/packages/hooks/src/lib.rs /dioxus/packages/hooks/src/use_callback.rs /dioxus/packages/hooks/src/use_context.rs /dioxus/packages/hooks/src/use_coroutine.rs /dioxus/packages/hooks/src/use_effect.rs /dioxus/packages/hooks/src/use_future.rs /dioxus/packages/hooks/src/use_hook_did_run.rs /dioxus/packages/hooks/src/use_memo.rs /dioxus/packages/hooks/src/use_on_destroy.rs /dioxus/packages/hooks/src/use_reactive.rs /dioxus/packages/hooks/src/use_resource.rs /dioxus/packages/hooks/src/use_root_context.rs /dioxus/packages/hooks/src/use_set_compare.rs /dioxus/packages/hooks/src/use_signal.rs /dioxus/packages/html/README.md /dioxus/packages/html/docs/common_event_handler_errors.md /dioxus/packages/html/docs/event_handlers.md /dioxus/packages/html/src/attribute_groups.rs /dioxus/packages/html/src/elements.rs /dioxus/packages/html/src/events/animation.rs /dioxus/packages/html/src/events/clipboard.rs /dioxus/packages/html/src/events/composition.rs /dioxus/packages/html/src/events/drag.rs /dioxus/packages/html/src/events/focus.rs /dioxus/packages/html/src/events/form.rs /dioxus/packages/html/src/events/image.rs /dioxus/packages/html/src/events/keyboard.rs /dioxus/packages/html/src/events/media.rs /dioxus/packages/html/src/events/mod.rs /dioxus/packages/html/src/events/mounted.rs /dioxus/packages/html/src/events/mouse.rs /dioxus/packages/html/src/events/pointer.rs /dioxus/packages/html/src/events/resize.rs /dioxus/packages/html/src/events/scroll.rs /dioxus/packages/html/src/events/selection.rs /dioxus/packages/html/src/events/toggle.rs /dioxus/packages/html/src/events/touch.rs /dioxus/packages/html/src/events/transition.rs /dioxus/packages/html/src/events/visible.rs /dioxus/packages/html/src/events/wheel.rs /dioxus/packages/html/src/file_data.rs /dioxus/packages/html/src/geometry.rs /dioxus/packages/html/src/input_data.rs /dioxus/packages/html/src/lib.rs /dioxus/packages/html/src/point_interaction.rs /dioxus/packages/html/src/render_template.rs /dioxus/packages/html-internal-macro/src/lib.rs /dioxus/packages/lazy-js-bundle/src/lib.rs /dioxus/packages/manganis/manganis/README.md /dioxus/packages/manganis/manganis/src/hash.rs /dioxus/packages/manganis/manganis/src/lib.rs /dioxus/packages/manganis/manganis/src/macro_helpers.rs /dioxus/packages/manganis/manganis-core/src/asset.rs /dioxus/packages/manganis/manganis-core/src/css.rs /dioxus/packages/manganis/manganis-core/src/folder.rs /dioxus/packages/manganis/manganis-core/src/hash.rs /dioxus/packages/manganis/manganis-core/src/images.rs /dioxus/packages/manganis/manganis-core/src/js.rs /dioxus/packages/manganis/manganis-core/src/lib.rs /dioxus/packages/manganis/manganis-core/src/linker.rs /dioxus/packages/manganis/manganis-core/src/options.rs /dioxus/packages/manganis/manganis-macro/README.md /dioxus/packages/manganis/manganis-macro/src/asset.rs /dioxus/packages/manganis/manganis-macro/src/lib.rs /dioxus/packages/manganis/manganis-macro/src/linker.rs /dioxus/packages/rsx/src/assign_dyn_ids.rs /dioxus/packages/rsx/src/attribute.rs /dioxus/packages/rsx/src/component.rs /dioxus/packages/rsx/src/diagnostics.rs /dioxus/packages/rsx/src/element.rs /dioxus/packages/rsx/src/expr_node.rs /dioxus/packages/rsx/src/forloop.rs /dioxus/packages/rsx/src/ifchain.rs /dioxus/packages/rsx/src/ifmt.rs /dioxus/packages/rsx/src/lib.rs /dioxus/packages/rsx/src/literal.rs /dioxus/packages/rsx/src/location.rs /dioxus/packages/rsx/src/node.rs /dioxus/packages/rsx/src/partial_closure.rs /dioxus/packages/rsx/src/raw_expr.rs /dioxus/packages/rsx/src/rsx_block.rs /dioxus/packages/rsx/src/rsx_call.rs /dioxus/packages/rsx/src/template_body.rs /dioxus/packages/rsx/src/text_node.rs /dioxus/packages/rsx/src/util.rs /dioxus/packages/rsx-hotreload/src/collect.rs /dioxus/packages/rsx-hotreload/src/diff.rs /dioxus/packages/rsx-hotreload/src/extensions.rs /dioxus/packages/rsx-hotreload/src/last_build_state.rs /dioxus/packages/rsx-hotreload/src/lib.rs /dioxus/packages/rsx-rosetta/README.md /dioxus/packages/rsx-rosetta/src/lib.rs /dioxus/packages/server-macro/src/lib.rs /dioxus/packages/signals/README.md /dioxus/packages/signals/docs/hoist/error.rs /dioxus/packages/signals/docs/hoist/fixed_list.rs /dioxus/packages/signals/docs/memo.md /dioxus/packages/signals/docs/signals.md /dioxus/packages/signals/src/copy_value.rs /dioxus/packages/signals/src/global/memo.rs /dioxus/packages/signals/src/global/mod.rs /dioxus/packages/signals/src/global/signal.rs /dioxus/packages/signals/src/impls.rs /dioxus/packages/signals/src/lib.rs /dioxus/packages/signals/src/map.rs /dioxus/packages/signals/src/memo.rs /dioxus/packages/signals/src/props.rs /dioxus/packages/signals/src/read.rs /dioxus/packages/signals/src/read_only_signal.rs /dioxus/packages/signals/src/set_compare.rs /dioxus/packages/signals/src/signal.rs /dioxus/packages/signals/src/warnings.rs /dioxus/packages/signals/src/write.rs /dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs diff --git a/packages/depinfo/src/lib.rs b/packages/depinfo/src/lib.rs index 8ddc8342a4..cdda8f17a1 100644 --- a/packages/depinfo/src/lib.rs +++ b/packages/depinfo/src/lib.rs @@ -116,323 +116,323 @@ mod tests { let contents = include_str!("./dx.d"); let info: RustcDepInfo = contents.parse().unwrap(); let answer = vec![ - "/Users/jonkelley/Development/dioxus/packages/autofmt/README.md", - "/Users/jonkelley/Development/dioxus/packages/autofmt/src/buffer.rs", - "/Users/jonkelley/Development/dioxus/packages/autofmt/src/collect_macros.rs", - "/Users/jonkelley/Development/dioxus/packages/autofmt/src/indent.rs", - "/Users/jonkelley/Development/dioxus/packages/autofmt/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/autofmt/src/prettier_please.rs", - "/Users/jonkelley/Development/dioxus/packages/autofmt/src/writer.rs", - "/Users/jonkelley/Development/dioxus/packages/check/README.md", - "/Users/jonkelley/Development/dioxus/packages/check/src/check.rs", - "/Users/jonkelley/Development/dioxus/packages/check/src/issues.rs", - "/Users/jonkelley/Development/dioxus/packages/check/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/check/src/metadata.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/README.md", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/MainActivity.kt.hbs", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/build.gradle.kts", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradle.properties", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradlew", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/gradlew.bat", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/android/gen/settings.gradle", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/dioxus.toml", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/ios/ios.plist.hbs", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/macos/mac.plist.hbs", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/web/index.html", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/web/loading.html", - "/Users/jonkelley/Development/dioxus/packages/cli/assets/web/toast.html", - "/Users/jonkelley/Development/dioxus/packages/cli/build.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/builder.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/bundle.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/prerender.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/progress.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/request.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/templates.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/verify.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/build/web.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/bundle_utils.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/autoformat.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/build.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/bundle.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/check.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/clean.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/config.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/create.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/init.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/link.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/run.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/serve.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/target.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/translate.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/cli/verbosity.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config/app.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config/bundle.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config/desktop.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config/dioxus_config.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config/serve.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config/web.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/config.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/dioxus_crate.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/dx_build_info.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/error.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/fastfs.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/filemap.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/logging.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/main.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/metadata.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/platform.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/rustc.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/ansi_buffer.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/detect.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/handle.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/output.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/proxy.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/runner.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/server.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/update.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/serve/watcher.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/settings.rs", - "/Users/jonkelley/Development/dioxus/packages/cli/src/wasm_bindgen.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-config/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/css.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/file.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/folder.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/image/jpg.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/image/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/image/png.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/js.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/json.rs", - "/Users/jonkelley/Development/dioxus/packages/cli-opt/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/config-macro/README.md", - "/Users/jonkelley/Development/dioxus/packages/config-macro/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/const-serialize/README.md", - "/Users/jonkelley/Development/dioxus/packages/const-serialize/src/const_buffers.rs", - "/Users/jonkelley/Development/dioxus/packages/const-serialize/src/const_vec.rs", - "/Users/jonkelley/Development/dioxus/packages/const-serialize/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/const-serialize-macro/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/core/README.md", - "/Users/jonkelley/Development/dioxus/packages/core/docs/common_spawn_errors.md", - "/Users/jonkelley/Development/dioxus/packages/core/docs/reactivity.md", - "/Users/jonkelley/Development/dioxus/packages/core/src/any_props.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/arena.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/diff/component.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/diff/iterator.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/diff/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/diff/node.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/effect.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/error_boundary.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/events.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/fragment.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/generational_box.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/global_context.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/hotreload_utils.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/launch.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/mutations.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/nodes.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/properties.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/reactive_context.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/render_error.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/root_wrapper.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/runtime.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/scheduler.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/scope_arena.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/scope_context.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/scopes.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/suspense/component.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/suspense/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/tasks.rs", - "/Users/jonkelley/Development/dioxus/packages/core/src/virtual_dom.rs", - "/Users/jonkelley/Development/dioxus/packages/core-macro/README.md", - "/Users/jonkelley/Development/dioxus/packages/core-macro/docs/component.md", - "/Users/jonkelley/Development/dioxus/packages/core-macro/docs/props.md", - "/Users/jonkelley/Development/dioxus/packages/core-macro/docs/rsx.md", - "/Users/jonkelley/Development/dioxus/packages/core-macro/src/component.rs", - "/Users/jonkelley/Development/dioxus/packages/core-macro/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/core-macro/src/props/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/core-macro/src/utils.rs", - "/Users/jonkelley/Development/dioxus/packages/core-types/src/bubbles.rs", - "/Users/jonkelley/Development/dioxus/packages/core-types/src/bundled.rs", - "/Users/jonkelley/Development/dioxus/packages/core-types/src/formatter.rs", - "/Users/jonkelley/Development/dioxus/packages/core-types/src/hr_context.rs", - "/Users/jonkelley/Development/dioxus/packages/core-types/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/devtools/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/devtools-types/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/dioxus-lib/README.md", - "/Users/jonkelley/Development/dioxus/packages/dioxus-lib/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/document/build.rs", - "/Users/jonkelley/Development/dioxus/packages/document/docs/eval.md", - "/Users/jonkelley/Development/dioxus/packages/document/docs/head.md", - "/Users/jonkelley/Development/dioxus/packages/document/src/document.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/link.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/meta.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/script.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/style.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/stylesheet.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/elements/title.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/error.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/eval.rs", - "/Users/jonkelley/Development/dioxus/packages/document/src/js/head.js", - "/Users/jonkelley/Development/dioxus/packages/document/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/document/./src/ts/eval.ts", - "/Users/jonkelley/Development/dioxus/packages/document/./src/ts/head.ts", - "/Users/jonkelley/Development/dioxus/packages/dx-wire-format/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/fullstack/README.md", - "/Users/jonkelley/Development/dioxus/packages/fullstack/src/document/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/fullstack/src/hooks/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/fullstack/src/hooks/server_cached.rs", - "/Users/jonkelley/Development/dioxus/packages/fullstack/src/hooks/server_future.rs", - "/Users/jonkelley/Development/dioxus/packages/fullstack/src/html_storage/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/fullstack/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/generational-box/README.md", - "/Users/jonkelley/Development/dioxus/packages/generational-box/src/entry.rs", - "/Users/jonkelley/Development/dioxus/packages/generational-box/src/error.rs", - "/Users/jonkelley/Development/dioxus/packages/generational-box/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/generational-box/src/references.rs", - "/Users/jonkelley/Development/dioxus/packages/generational-box/src/sync.rs", - "/Users/jonkelley/Development/dioxus/packages/generational-box/src/unsync.rs", - "/Users/jonkelley/Development/dioxus/packages/history/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/history/src/memory.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/README.md", - "/Users/jonkelley/Development/dioxus/packages/hooks/docs/derived_state.md", - "/Users/jonkelley/Development/dioxus/packages/hooks/docs/moving_state_around.md", - "/Users/jonkelley/Development/dioxus/packages/hooks/docs/rules_of_hooks.md", - "/Users/jonkelley/Development/dioxus/packages/hooks/docs/side_effects.md", - "/Users/jonkelley/Development/dioxus/packages/hooks/docs/use_resource.md", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_callback.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_context.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_coroutine.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_effect.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_future.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_hook_did_run.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_memo.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_on_destroy.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_reactive.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_resource.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_root_context.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_set_compare.rs", - "/Users/jonkelley/Development/dioxus/packages/hooks/src/use_signal.rs", - "/Users/jonkelley/Development/dioxus/packages/html/README.md", - "/Users/jonkelley/Development/dioxus/packages/html/docs/common_event_handler_errors.md", - "/Users/jonkelley/Development/dioxus/packages/html/docs/event_handlers.md", - "/Users/jonkelley/Development/dioxus/packages/html/src/attribute_groups.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/elements.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/animation.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/clipboard.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/composition.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/drag.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/focus.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/form.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/image.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/keyboard.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/media.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/mounted.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/mouse.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/pointer.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/resize.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/scroll.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/selection.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/toggle.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/touch.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/transition.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/visible.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/events/wheel.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/file_data.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/geometry.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/input_data.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/point_interaction.rs", - "/Users/jonkelley/Development/dioxus/packages/html/src/render_template.rs", - "/Users/jonkelley/Development/dioxus/packages/html-internal-macro/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/lazy-js-bundle/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis/README.md", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis/src/hash.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis/src/macro_helpers.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/asset.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/css.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/folder.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/hash.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/images.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/js.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/linker.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-core/src/options.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/README.md", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/src/asset.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/manganis/manganis-macro/src/linker.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/assign_dyn_ids.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/attribute.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/component.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/diagnostics.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/element.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/expr_node.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/forloop.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/ifchain.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/ifmt.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/literal.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/location.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/node.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/partial_closure.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/raw_expr.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/rsx_block.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/rsx_call.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/template_body.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/text_node.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx/src/util.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/collect.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/diff.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/extensions.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/last_build_state.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx-hotreload/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/rsx-rosetta/README.md", - "/Users/jonkelley/Development/dioxus/packages/rsx-rosetta/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/server-macro/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/README.md", - "/Users/jonkelley/Development/dioxus/packages/signals/docs/hoist/error.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/docs/hoist/fixed_list.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/docs/memo.md", - "/Users/jonkelley/Development/dioxus/packages/signals/docs/signals.md", - "/Users/jonkelley/Development/dioxus/packages/signals/src/copy_value.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/global/memo.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/global/mod.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/global/signal.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/impls.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/lib.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/map.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/memo.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/props.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/read.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/read_only_signal.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/set_compare.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/signal.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/warnings.rs", - "/Users/jonkelley/Development/dioxus/packages/signals/src/write.rs", - "/Users/jonkelley/Development/dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs", + "/dioxus/packages/autofmt/README.md", + "/dioxus/packages/autofmt/src/buffer.rs", + "/dioxus/packages/autofmt/src/collect_macros.rs", + "/dioxus/packages/autofmt/src/indent.rs", + "/dioxus/packages/autofmt/src/lib.rs", + "/dioxus/packages/autofmt/src/prettier_please.rs", + "/dioxus/packages/autofmt/src/writer.rs", + "/dioxus/packages/check/README.md", + "/dioxus/packages/check/src/check.rs", + "/dioxus/packages/check/src/issues.rs", + "/dioxus/packages/check/src/lib.rs", + "/dioxus/packages/check/src/metadata.rs", + "/dioxus/packages/cli/README.md", + "/dioxus/packages/cli/assets/android/MainActivity.kt.hbs", + "/dioxus/packages/cli/assets/android/gen/app/build.gradle.kts.hbs", + "/dioxus/packages/cli/assets/android/gen/app/proguard-rules.pro", + "/dioxus/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/colors.xml", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs", + "/dioxus/packages/cli/assets/android/gen/app/src/main/res/values/styles.xml", + "/dioxus/packages/cli/assets/android/gen/build.gradle.kts", + "/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar", + "/dioxus/packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties", + "/dioxus/packages/cli/assets/android/gen/gradle.properties", + "/dioxus/packages/cli/assets/android/gen/gradlew", + "/dioxus/packages/cli/assets/android/gen/gradlew.bat", + "/dioxus/packages/cli/assets/android/gen/settings.gradle", + "/dioxus/packages/cli/assets/dioxus.toml", + "/dioxus/packages/cli/assets/ios/ios.plist.hbs", + "/dioxus/packages/cli/assets/macos/mac.plist.hbs", + "/dioxus/packages/cli/assets/web/index.html", + "/dioxus/packages/cli/assets/web/loading.html", + "/dioxus/packages/cli/assets/web/toast.html", + "/dioxus/packages/cli/build.rs", + "/dioxus/packages/cli/src/build/builder.rs", + "/dioxus/packages/cli/src/build/bundle.rs", + "/dioxus/packages/cli/src/build/mod.rs", + "/dioxus/packages/cli/src/build/prerender.rs", + "/dioxus/packages/cli/src/build/progress.rs", + "/dioxus/packages/cli/src/build/request.rs", + "/dioxus/packages/cli/src/build/templates.rs", + "/dioxus/packages/cli/src/build/verify.rs", + "/dioxus/packages/cli/src/build/web.rs", + "/dioxus/packages/cli/src/bundle_utils.rs", + "/dioxus/packages/cli/src/cli/autoformat.rs", + "/dioxus/packages/cli/src/cli/build.rs", + "/dioxus/packages/cli/src/cli/bundle.rs", + "/dioxus/packages/cli/src/cli/check.rs", + "/dioxus/packages/cli/src/cli/clean.rs", + "/dioxus/packages/cli/src/cli/config.rs", + "/dioxus/packages/cli/src/cli/create.rs", + "/dioxus/packages/cli/src/cli/init.rs", + "/dioxus/packages/cli/src/cli/link.rs", + "/dioxus/packages/cli/src/cli/mod.rs", + "/dioxus/packages/cli/src/cli/run.rs", + "/dioxus/packages/cli/src/cli/serve.rs", + "/dioxus/packages/cli/src/cli/target.rs", + "/dioxus/packages/cli/src/cli/translate.rs", + "/dioxus/packages/cli/src/cli/verbosity.rs", + "/dioxus/packages/cli/src/config/app.rs", + "/dioxus/packages/cli/src/config/bundle.rs", + "/dioxus/packages/cli/src/config/desktop.rs", + "/dioxus/packages/cli/src/config/dioxus_config.rs", + "/dioxus/packages/cli/src/config/serve.rs", + "/dioxus/packages/cli/src/config/web.rs", + "/dioxus/packages/cli/src/config.rs", + "/dioxus/packages/cli/src/dioxus_crate.rs", + "/dioxus/packages/cli/src/dx_build_info.rs", + "/dioxus/packages/cli/src/error.rs", + "/dioxus/packages/cli/src/fastfs.rs", + "/dioxus/packages/cli/src/filemap.rs", + "/dioxus/packages/cli/src/logging.rs", + "/dioxus/packages/cli/src/main.rs", + "/dioxus/packages/cli/src/metadata.rs", + "/dioxus/packages/cli/src/platform.rs", + "/dioxus/packages/cli/src/rustc.rs", + "/dioxus/packages/cli/src/serve/ansi_buffer.rs", + "/dioxus/packages/cli/src/serve/detect.rs", + "/dioxus/packages/cli/src/serve/handle.rs", + "/dioxus/packages/cli/src/serve/mod.rs", + "/dioxus/packages/cli/src/serve/output.rs", + "/dioxus/packages/cli/src/serve/proxy.rs", + "/dioxus/packages/cli/src/serve/runner.rs", + "/dioxus/packages/cli/src/serve/server.rs", + "/dioxus/packages/cli/src/serve/update.rs", + "/dioxus/packages/cli/src/serve/watcher.rs", + "/dioxus/packages/cli/src/settings.rs", + "/dioxus/packages/cli/src/wasm_bindgen.rs", + "/dioxus/packages/cli-config/src/lib.rs", + "/dioxus/packages/cli-opt/src/css.rs", + "/dioxus/packages/cli-opt/src/file.rs", + "/dioxus/packages/cli-opt/src/folder.rs", + "/dioxus/packages/cli-opt/src/image/jpg.rs", + "/dioxus/packages/cli-opt/src/image/mod.rs", + "/dioxus/packages/cli-opt/src/image/png.rs", + "/dioxus/packages/cli-opt/src/js.rs", + "/dioxus/packages/cli-opt/src/json.rs", + "/dioxus/packages/cli-opt/src/lib.rs", + "/dioxus/packages/config-macro/README.md", + "/dioxus/packages/config-macro/src/lib.rs", + "/dioxus/packages/const-serialize/README.md", + "/dioxus/packages/const-serialize/src/const_buffers.rs", + "/dioxus/packages/const-serialize/src/const_vec.rs", + "/dioxus/packages/const-serialize/src/lib.rs", + "/dioxus/packages/const-serialize-macro/src/lib.rs", + "/dioxus/packages/core/README.md", + "/dioxus/packages/core/docs/common_spawn_errors.md", + "/dioxus/packages/core/docs/reactivity.md", + "/dioxus/packages/core/src/any_props.rs", + "/dioxus/packages/core/src/arena.rs", + "/dioxus/packages/core/src/diff/component.rs", + "/dioxus/packages/core/src/diff/iterator.rs", + "/dioxus/packages/core/src/diff/mod.rs", + "/dioxus/packages/core/src/diff/node.rs", + "/dioxus/packages/core/src/effect.rs", + "/dioxus/packages/core/src/error_boundary.rs", + "/dioxus/packages/core/src/events.rs", + "/dioxus/packages/core/src/fragment.rs", + "/dioxus/packages/core/src/generational_box.rs", + "/dioxus/packages/core/src/global_context.rs", + "/dioxus/packages/core/src/hotreload_utils.rs", + "/dioxus/packages/core/src/launch.rs", + "/dioxus/packages/core/src/lib.rs", + "/dioxus/packages/core/src/mutations.rs", + "/dioxus/packages/core/src/nodes.rs", + "/dioxus/packages/core/src/properties.rs", + "/dioxus/packages/core/src/reactive_context.rs", + "/dioxus/packages/core/src/render_error.rs", + "/dioxus/packages/core/src/root_wrapper.rs", + "/dioxus/packages/core/src/runtime.rs", + "/dioxus/packages/core/src/scheduler.rs", + "/dioxus/packages/core/src/scope_arena.rs", + "/dioxus/packages/core/src/scope_context.rs", + "/dioxus/packages/core/src/scopes.rs", + "/dioxus/packages/core/src/suspense/component.rs", + "/dioxus/packages/core/src/suspense/mod.rs", + "/dioxus/packages/core/src/tasks.rs", + "/dioxus/packages/core/src/virtual_dom.rs", + "/dioxus/packages/core-macro/README.md", + "/dioxus/packages/core-macro/docs/component.md", + "/dioxus/packages/core-macro/docs/props.md", + "/dioxus/packages/core-macro/docs/rsx.md", + "/dioxus/packages/core-macro/src/component.rs", + "/dioxus/packages/core-macro/src/lib.rs", + "/dioxus/packages/core-macro/src/props/mod.rs", + "/dioxus/packages/core-macro/src/utils.rs", + "/dioxus/packages/core-types/src/bubbles.rs", + "/dioxus/packages/core-types/src/bundled.rs", + "/dioxus/packages/core-types/src/formatter.rs", + "/dioxus/packages/core-types/src/hr_context.rs", + "/dioxus/packages/core-types/src/lib.rs", + "/dioxus/packages/devtools/src/lib.rs", + "/dioxus/packages/devtools-types/src/lib.rs", + "/dioxus/packages/dioxus-lib/README.md", + "/dioxus/packages/dioxus-lib/src/lib.rs", + "/dioxus/packages/document/build.rs", + "/dioxus/packages/document/docs/eval.md", + "/dioxus/packages/document/docs/head.md", + "/dioxus/packages/document/src/document.rs", + "/dioxus/packages/document/src/elements/link.rs", + "/dioxus/packages/document/src/elements/meta.rs", + "/dioxus/packages/document/src/elements/mod.rs", + "/dioxus/packages/document/src/elements/script.rs", + "/dioxus/packages/document/src/elements/style.rs", + "/dioxus/packages/document/src/elements/stylesheet.rs", + "/dioxus/packages/document/src/elements/title.rs", + "/dioxus/packages/document/src/error.rs", + "/dioxus/packages/document/src/eval.rs", + "/dioxus/packages/document/src/js/head.js", + "/dioxus/packages/document/src/lib.rs", + "/dioxus/packages/document/./src/ts/eval.ts", + "/dioxus/packages/document/./src/ts/head.ts", + "/dioxus/packages/dx-wire-format/src/lib.rs", + "/dioxus/packages/fullstack/README.md", + "/dioxus/packages/fullstack/src/document/mod.rs", + "/dioxus/packages/fullstack/src/hooks/mod.rs", + "/dioxus/packages/fullstack/src/hooks/server_cached.rs", + "/dioxus/packages/fullstack/src/hooks/server_future.rs", + "/dioxus/packages/fullstack/src/html_storage/mod.rs", + "/dioxus/packages/fullstack/src/lib.rs", + "/dioxus/packages/generational-box/README.md", + "/dioxus/packages/generational-box/src/entry.rs", + "/dioxus/packages/generational-box/src/error.rs", + "/dioxus/packages/generational-box/src/lib.rs", + "/dioxus/packages/generational-box/src/references.rs", + "/dioxus/packages/generational-box/src/sync.rs", + "/dioxus/packages/generational-box/src/unsync.rs", + "/dioxus/packages/history/src/lib.rs", + "/dioxus/packages/history/src/memory.rs", + "/dioxus/packages/hooks/README.md", + "/dioxus/packages/hooks/docs/derived_state.md", + "/dioxus/packages/hooks/docs/moving_state_around.md", + "/dioxus/packages/hooks/docs/rules_of_hooks.md", + "/dioxus/packages/hooks/docs/side_effects.md", + "/dioxus/packages/hooks/docs/use_resource.md", + "/dioxus/packages/hooks/src/lib.rs", + "/dioxus/packages/hooks/src/use_callback.rs", + "/dioxus/packages/hooks/src/use_context.rs", + "/dioxus/packages/hooks/src/use_coroutine.rs", + "/dioxus/packages/hooks/src/use_effect.rs", + "/dioxus/packages/hooks/src/use_future.rs", + "/dioxus/packages/hooks/src/use_hook_did_run.rs", + "/dioxus/packages/hooks/src/use_memo.rs", + "/dioxus/packages/hooks/src/use_on_destroy.rs", + "/dioxus/packages/hooks/src/use_reactive.rs", + "/dioxus/packages/hooks/src/use_resource.rs", + "/dioxus/packages/hooks/src/use_root_context.rs", + "/dioxus/packages/hooks/src/use_set_compare.rs", + "/dioxus/packages/hooks/src/use_signal.rs", + "/dioxus/packages/html/README.md", + "/dioxus/packages/html/docs/common_event_handler_errors.md", + "/dioxus/packages/html/docs/event_handlers.md", + "/dioxus/packages/html/src/attribute_groups.rs", + "/dioxus/packages/html/src/elements.rs", + "/dioxus/packages/html/src/events/animation.rs", + "/dioxus/packages/html/src/events/clipboard.rs", + "/dioxus/packages/html/src/events/composition.rs", + "/dioxus/packages/html/src/events/drag.rs", + "/dioxus/packages/html/src/events/focus.rs", + "/dioxus/packages/html/src/events/form.rs", + "/dioxus/packages/html/src/events/image.rs", + "/dioxus/packages/html/src/events/keyboard.rs", + "/dioxus/packages/html/src/events/media.rs", + "/dioxus/packages/html/src/events/mod.rs", + "/dioxus/packages/html/src/events/mounted.rs", + "/dioxus/packages/html/src/events/mouse.rs", + "/dioxus/packages/html/src/events/pointer.rs", + "/dioxus/packages/html/src/events/resize.rs", + "/dioxus/packages/html/src/events/scroll.rs", + "/dioxus/packages/html/src/events/selection.rs", + "/dioxus/packages/html/src/events/toggle.rs", + "/dioxus/packages/html/src/events/touch.rs", + "/dioxus/packages/html/src/events/transition.rs", + "/dioxus/packages/html/src/events/visible.rs", + "/dioxus/packages/html/src/events/wheel.rs", + "/dioxus/packages/html/src/file_data.rs", + "/dioxus/packages/html/src/geometry.rs", + "/dioxus/packages/html/src/input_data.rs", + "/dioxus/packages/html/src/lib.rs", + "/dioxus/packages/html/src/point_interaction.rs", + "/dioxus/packages/html/src/render_template.rs", + "/dioxus/packages/html-internal-macro/src/lib.rs", + "/dioxus/packages/lazy-js-bundle/src/lib.rs", + "/dioxus/packages/manganis/manganis/README.md", + "/dioxus/packages/manganis/manganis/src/hash.rs", + "/dioxus/packages/manganis/manganis/src/lib.rs", + "/dioxus/packages/manganis/manganis/src/macro_helpers.rs", + "/dioxus/packages/manganis/manganis-core/src/asset.rs", + "/dioxus/packages/manganis/manganis-core/src/css.rs", + "/dioxus/packages/manganis/manganis-core/src/folder.rs", + "/dioxus/packages/manganis/manganis-core/src/hash.rs", + "/dioxus/packages/manganis/manganis-core/src/images.rs", + "/dioxus/packages/manganis/manganis-core/src/js.rs", + "/dioxus/packages/manganis/manganis-core/src/lib.rs", + "/dioxus/packages/manganis/manganis-core/src/linker.rs", + "/dioxus/packages/manganis/manganis-core/src/options.rs", + "/dioxus/packages/manganis/manganis-macro/README.md", + "/dioxus/packages/manganis/manganis-macro/src/asset.rs", + "/dioxus/packages/manganis/manganis-macro/src/lib.rs", + "/dioxus/packages/manganis/manganis-macro/src/linker.rs", + "/dioxus/packages/rsx/src/assign_dyn_ids.rs", + "/dioxus/packages/rsx/src/attribute.rs", + "/dioxus/packages/rsx/src/component.rs", + "/dioxus/packages/rsx/src/diagnostics.rs", + "/dioxus/packages/rsx/src/element.rs", + "/dioxus/packages/rsx/src/expr_node.rs", + "/dioxus/packages/rsx/src/forloop.rs", + "/dioxus/packages/rsx/src/ifchain.rs", + "/dioxus/packages/rsx/src/ifmt.rs", + "/dioxus/packages/rsx/src/lib.rs", + "/dioxus/packages/rsx/src/literal.rs", + "/dioxus/packages/rsx/src/location.rs", + "/dioxus/packages/rsx/src/node.rs", + "/dioxus/packages/rsx/src/partial_closure.rs", + "/dioxus/packages/rsx/src/raw_expr.rs", + "/dioxus/packages/rsx/src/rsx_block.rs", + "/dioxus/packages/rsx/src/rsx_call.rs", + "/dioxus/packages/rsx/src/template_body.rs", + "/dioxus/packages/rsx/src/text_node.rs", + "/dioxus/packages/rsx/src/util.rs", + "/dioxus/packages/rsx-hotreload/src/collect.rs", + "/dioxus/packages/rsx-hotreload/src/diff.rs", + "/dioxus/packages/rsx-hotreload/src/extensions.rs", + "/dioxus/packages/rsx-hotreload/src/last_build_state.rs", + "/dioxus/packages/rsx-hotreload/src/lib.rs", + "/dioxus/packages/rsx-rosetta/README.md", + "/dioxus/packages/rsx-rosetta/src/lib.rs", + "/dioxus/packages/server-macro/src/lib.rs", + "/dioxus/packages/signals/README.md", + "/dioxus/packages/signals/docs/hoist/error.rs", + "/dioxus/packages/signals/docs/hoist/fixed_list.rs", + "/dioxus/packages/signals/docs/memo.md", + "/dioxus/packages/signals/docs/signals.md", + "/dioxus/packages/signals/src/copy_value.rs", + "/dioxus/packages/signals/src/global/memo.rs", + "/dioxus/packages/signals/src/global/mod.rs", + "/dioxus/packages/signals/src/global/signal.rs", + "/dioxus/packages/signals/src/impls.rs", + "/dioxus/packages/signals/src/lib.rs", + "/dioxus/packages/signals/src/map.rs", + "/dioxus/packages/signals/src/memo.rs", + "/dioxus/packages/signals/src/props.rs", + "/dioxus/packages/signals/src/read.rs", + "/dioxus/packages/signals/src/read_only_signal.rs", + "/dioxus/packages/signals/src/set_compare.rs", + "/dioxus/packages/signals/src/signal.rs", + "/dioxus/packages/signals/src/warnings.rs", + "/dioxus/packages/signals/src/write.rs", + "/dioxus/target/debug/build/dioxus-cli-90993e55e02b7cee/out/built.rs", ]; assert_eq!( answer.iter().map(PathBuf::from).collect::>(), diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index f198c0dc60..fc5c9a138c 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -102,7 +102,7 @@ async fn serve_server( let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - tracing::info!("Listening on {address} with listener {listener:?}"); + tracing::debug!("Listening on {address}"); enum Msg { TcpStream(std::io::Result<(TcpStream, SocketAddr)>), @@ -210,27 +210,11 @@ async fn serve_server( .await .unwrap_or_else(|err| match err {}); - // todo - this was taken from axum::serve but it seems like IncomingStream serves no purpose? - #[derive(Debug)] - pub struct IncomingStream_<'a> { - tcp_stream: &'a TokioIo, - remote_addr: SocketAddr, - } - let tower_service = make_service - .call(IncomingStream_ { - tcp_stream: &tcp_stream, - remote_addr, - }) + .call(()) .await .unwrap_or_else(|err| match err {}) - .map_request(|req: Request| { - let req = req.map(Body::new); - - tracing::info!("Handling request: {:?}", req); - - req - }); + .map_request(|req: Request| req.map(Body::new)); // upgrades needed for websockets let builder = HyperBuilder::new(TokioExecutor::new()); diff --git a/packages/server/src/rt.rs b/packages/server/src/rt.rs index f2aecc4b6e..5ad15b4bc8 100644 --- a/packages/server/src/rt.rs +++ b/packages/server/src/rt.rs @@ -186,7 +186,7 @@ where mut self, context_providers: ContextProviders, ) -> Self { - tracing::info!("Registering server functions..."); + tracing::trace!("Registering server functions..."); for f in collect_raw_server_fns() { self = register_server_fn_on_router(f, self, context_providers.clone()); diff --git a/packages/subsecond/subsecond-cli-support/src/lib.rs b/packages/subsecond/subsecond-cli-support/src/lib.rs index 36c8ce55bf..37365f7f85 100644 --- a/packages/subsecond/subsecond-cli-support/src/lib.rs +++ b/packages/subsecond/subsecond-cli-support/src/lib.rs @@ -1349,28 +1349,3 @@ fn make_stub_file( ) .unwrap() } - -// /// Move all previous object files to "incremental-old" and all new object files to "incremental-new" -// fn cache_incrementals(object_files: &[&String]) { -// let old = subsecond_folder().join("data").join("incremental-old"); -// let new = subsecond_folder().join("data").join("incremental-new"); - -// // Remove the old incremental-old directory if it exists -// _ = std::fs::remove_dir_all(&old); - -// // Rename incremental-new to incremental-old if it exists. Faster than moving all the files -// _ = std::fs::rename(&new, &old); - -// // Create the new incremental-new directory to place the outputs in -// std::fs::create_dir_all(&new).unwrap(); - -// // Now drop in all the new object files -// for o in object_files.iter() { -// if !o.ends_with(".rcgu.o") { -// continue; -// } - -// let path = PathBuf::from(o); -// std::fs::copy(&path, new.join(path.file_name().unwrap())).unwrap(); -// } -// } diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index 70527da246..aa1fb85773 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -209,7 +209,6 @@ use std::{ sync::{Arc, Mutex}, }; -pub use subsecond_macro::hot; pub use subsecond_types::JumpTable; // todo: if there's a reference held while we run our patch, this gets invalidated. should probably From f7ddc598e46339357efb41e7d9f43246d75af8e9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 13:13:39 -0700 Subject: [PATCH 136/301] lots more documentation, for posterity! --- packages/cli/src/build/context.rs | 11 +- packages/cli/src/build/request.rs | 435 +++++++++++++++++++----------- packages/cli/src/cli/build.rs | 18 +- 3 files changed, 300 insertions(+), 164 deletions(-) diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index 11aa03ae96..aa3b8589bc 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -1,5 +1,5 @@ //! Report progress about the build to the user. We use channels to report progress back to the CLI. -use crate::{BuildArtifacts, BuildRequest, BuildStage, Error, Platform, TraceSrc}; +use crate::{BuildArtifacts, BuildStage, Error, TraceSrc}; use cargo_metadata::CompilerMessage; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::{path::PathBuf, process::ExitStatus}; @@ -9,20 +9,22 @@ use super::BuildMode; /// The context of the build process. While the BuildRequest is a "plan" for the build, the BuildContext /// provides some dynamic configuration that is only known at runtime. For example, the Progress channel /// and the BuildMode can change while serving. +/// +/// The structure of this is roughly taken from cargo itself which uses a similar pattern. #[derive(Debug, Clone)] pub struct BuildContext { pub tx: ProgressTx, pub mode: BuildMode, } -pub(crate) type ProgressTx = UnboundedSender; -pub(crate) type ProgressRx = UnboundedReceiver; +pub type ProgressTx = UnboundedSender; +pub type ProgressRx = UnboundedReceiver; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuildId(pub usize); #[allow(clippy::large_enum_variant)] -pub(crate) enum BuildUpdate { +pub enum BuildUpdate { Progress { stage: BuildStage, }, @@ -74,7 +76,6 @@ impl BuildContext { } pub(crate) fn status_start_bundle(&self) { - tracing::debug!("Assembling app bundle"); _ = self.tx.unbounded_send(BuildUpdate::Progress { stage: BuildStage::Bundling {}, }); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 531848234b..93a6450a23 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -310,7 +310,10 @@ pub(crate) struct BuildRequest { pub(crate) features: Vec, /// Extra arguments to pass to cargo - pub(crate) cargo_args: Vec, + pub(crate) extra_cargo_args: Vec, + + /// Extra arguments to pass to rustc + pub(crate) extra_rustc_args: Vec, /// Don't include the default features in the build pub(crate) no_default_features: bool, @@ -572,7 +575,7 @@ impl BuildRequest { None }; - // Set up some tempfiles so we can do some IPC between us and the linker (which is occasionally us!) + // 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::new().context("Failed to create temporary file for linker args")?, ); @@ -600,7 +603,8 @@ impl BuildRequest { link_args_file, link_err_file, rustc_wrapper_args_file, - cargo_args: args.cargo_args.clone(), + extra_rustc_args: args.rustc_args.clone(), + extra_cargo_args: args.cargo_args.clone(), nightly: args.nightly, cargo_package: package, release: args.release, @@ -623,7 +627,7 @@ impl BuildRequest { // Write the build artifacts to the bundle on the disk match ctx.mode { BuildMode::Thin { aslr_reference, .. } => { - self.write_patch(ctx, aslr_reference, artifacts.time_start) + self.write_patch(ctx, aslr_reference, &mut artifacts) .await?; } @@ -649,6 +653,10 @@ impl BuildRequest { Ok(artifacts) } + /// Run the cargo build by assembling the build command and executing it. + /// + /// This method needs to be very careful with processing output since errors being swallowed will + /// be very confusing to the user. async fn cargo_build(&self, ctx: &BuildContext) -> Result { let time_start = SystemTime::now(); @@ -696,13 +704,22 @@ impl BuildRequest { match message { Message::BuildScriptExecuted(_) => units_compiled += 1, + Message::CompilerMessage(msg) => ctx.status_build_diagnostic(msg), Message::TextLine(line) => { + // Handle the case where we're getting lines directly from rustc. + // These are in a different format than the normal cargo output, though I imagine + // this parsing code is quite fragile/sensitive to changes in cargo, cargo_metadta, rustc, etc. #[derive(Deserialize)] struct RustcArtifact { artifact: PathBuf, emit: String, } + // These outputs look something like: + // + // { "artifact":"target/debug/deps/libdioxus_core-4f2a0b3c1e5f8b7c.rlib", "emit":"link" } + // + // There are other outputs like depinfo that we might be interested in in the future. if let Ok(artifact) = serde_json::from_str::(&line) { if artifact.emit == "link" { output_location = Some(artifact.artifact); @@ -713,17 +730,21 @@ impl BuildRequest { // instead of an "error" message. However, the following messages *also* tend to // be the error message, and don't start with "error:". So we'll check if we've already // emitted an error message and if so, we'll emit all following messages as errors too. + // + // todo: This can lead to some really ugly output though, so we might want to look + // into a more reliable way to detect errors propagating out of the compiler. If + // we always wrapped rustc, then we could store this data somewhere in a much more + // reliable format. if line.trim_start().starts_with("error:") { emitting_error = true; } - if emitting_error { - ctx.status_build_error(line); - } else { - ctx.status_build_message(line) + // Note that previous text lines might have set emitting_error to true + match emitting_error { + true => ctx.status_build_error(line), + false => ctx.status_build_message(line), } } - Message::CompilerMessage(msg) => ctx.status_build_diagnostic(msg), Message::CompilerArtifact(artifact) => { units_compiled += 1; match artifact.executable { @@ -735,6 +756,8 @@ impl BuildRequest { ), } } + // todo: this can occasionally swallow errors, so we should figure out what exactly is going wrong + // since that is a really bad user experience. Message::BuildFinished(finished) => { if !finished.success { return Err(anyhow::anyhow!( @@ -755,6 +778,7 @@ impl BuildRequest { let assets = self.collect_assets(&exe)?; let time_end = SystemTime::now(); let mode = ctx.mode.clone(); + let platform = self.platform; tracing::debug!("Build completed successfully - output location: {:?}", exe); // Accumulate the rustc args from the wrapper, if they exist and can be parsed. @@ -766,7 +790,7 @@ impl BuildRequest { } Ok(BuildArtifacts { - platform: self.platform, + platform, exe, direct_rustc, time_start, @@ -781,17 +805,15 @@ impl BuildRequest { /// This uses "known paths" that have stayed relatively stable during cargo's lifetime. /// One day this system might break and we might need to go back to using the linker approach. fn collect_assets(&self, exe: &Path) -> Result { - tracing::debug!("Collecting assets ..."); - - if self.skip_assets { - return Ok(AssetManifest::default()); - } + tracing::debug!("Collecting assets from exe at {} ...", exe.display()); // walk every file in the incremental cache dir, reading and inserting items into the manifest. let mut manifest = AssetManifest::default(); // And then add from the exe directly, just in case it's LTO compiled and has no incremental cache - _ = manifest.add_from_object_path(exe); + if !self.skip_assets { + _ = manifest.add_from_object_path(exe); + } Ok(manifest) } @@ -999,10 +1021,22 @@ impl BuildRequest { Ok(()) } - /// libpatch-{time}.(so/dll/dylib) (next to the main exe) + /// Patches are stored in the same directory as the main executable, but with a name based on the + /// time the patch started compiling. + /// + /// - lib{name}-patch-{time}.(so/dll/dylib) (next to the main exe) + /// + /// Note that weirdly enough, the name of dylibs can actually matter. In some environments, libs + /// can override each other with symbol interposition. + /// + /// Also, on Android - and some Linux, we *need* to start the lib name with `lib` for the dynamic + /// loader to consider it a shared library. + /// + /// todo: the time format might actually be problematic if two platforms share the same build folder. pub(crate) fn patch_exe(&self, time_start: SystemTime) -> PathBuf { let path = self.main_exe().with_file_name(format!( - "libpatch-{}", + "lib{}-patch-{}", + self.executable_name(), time_start.duration_since(UNIX_EPOCH).unwrap().as_millis(), )); @@ -1010,10 +1044,10 @@ impl BuildRequest { OperatingSystem::Darwin(_) => "dylib", OperatingSystem::MacOSX(_) => "dylib", OperatingSystem::IOS(_) => "dylib", - OperatingSystem::Unknown if self.platform == Platform::Web => "wasm", OperatingSystem::Windows => "dll", OperatingSystem::Linux => "so", OperatingSystem::Wasi => "wasm", + OperatingSystem::Unknown if self.platform == Platform::Web => "wasm", _ => "", }; @@ -1021,22 +1055,72 @@ impl BuildRequest { } /// Run our custom linker setup to generate a patch file in the right location + /// + /// This should be the only case where the cargo output is a "dummy" file and requires us to + /// manually do any linking. + /// + /// We also run some post processing steps here, like extracting out any new assets. async fn write_patch( &self, _ctx: &BuildContext, aslr_reference: Option, - time_start: SystemTime, + artifacts: &mut BuildArtifacts, ) -> Result<()> { tracing::debug!("Patching existing bundle"); + let time_start = artifacts.time_start; let raw_args = std::fs::read_to_string(&self.link_args_file()) .context("Failed to read link args from file")?; - let args = raw_args.lines().collect::>(); - let orig_exe = self.main_exe(); - tracing::debug!("writing patch - orig_exe: {:?}", orig_exe); + // Extract out the incremental object files. + // + // This is sadly somewhat of a hack, but it might be a moderately reliable hack. + // + // When rustc links your project, it passes the args as how a linker would expect, but with + // a somehwat reliable ordering. These are all internal details to cargo/rustc, so we can't + // rely on them *too* much, but the *are* fundamental to how rust compiles your projects, and + // linker interfaces probably won't change drastically for another 40 years. + // + // We need to tear apart this command andonly pass the args that are relevant to our thin link. + // Mainly, we don't want any rlibs to be linked. Occasionally some libraries like objc_exception + // export a folder with their artifacts - unsure if we actually need to include them. Generally + // you can err on the side that most *libraries* don't need to be linked here since dlopen + // satisfies those symbols anyways. + // + // Many args are passed twice, too, which can be confusing, but generally don't have any real + // effect. Note that on macos/ios, there's a special macho header that *needs* to be set. + // + // ``` + // cc + // /dioxus/target/debug/subsecond-cli + // /var/folders/zs/gvrfkj8x33d39cvw2p06yc700000gn/T/rustcAqQ4p2/symbols.o + // /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.05stnb4bovskp7a00wyyf7l9s.rcgu.o + // /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.08rgcutgrtj2mxoogjg3ufs0g.rcgu.o + // /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.0941bd8fa2bydcv9hfmgzzne9.rcgu.o + // /dioxus/target/subsecond-dev/deps/libbincode-c215feeb7886f81b.rlib + // /dioxus/target/subsecond-dev/deps/libanyhow-e69ac15c094daba6.rlib + // /dioxus/target/subsecond-dev/deps/libratatui-c3364579b86a1dfc.rlib + // /.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libstd-019f0f6ae6e6562b.rlib + // /.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libpanic_unwind-7387d38173a2eb37.rlib + // /.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libobject-2b03cf6ece171d21.rlib + // -framework AppKit + // -lc + // -framework Foundation + // -framework Carbon + // -lSystem + // -framework CoreFoundation + // -lobjc + // -liconv + // -lm + // -arch arm64 + // -mmacosx-version-min=11.0.0 + // -L /dioxus/target/subsecond-dev/build/objc_exception-dc226cad0480ea65/out + // -o /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa + // -nodefaultlibs + // -Wl,-all_load + // ``` let mut object_files = args .iter() .filter(|arg| arg.ends_with(".rcgu.o")) @@ -1047,26 +1131,35 @@ impl BuildRequest { // Our wasm approach is quite specific to wasm. We don't need to resolve any missing symbols // there since wasm is relocatable, but there is considerable pre and post processing work to get it // working. + // + // On non-wasm platforms, we generate a special shim object file which converts symbols from + // fat binary into direct addresses from the running process. + // + // todo: don't require the aslr reference and just patch the got when loading. + // + // Requiring the ASLR offset here is necessary but unfortunately might be flakey in practice. + // Android apps can take a long time to open, and a hot patch might've been issued in the interim, + // making this hotpatch a failure. if self.platform != Platform::Web { - let resolved_patch_bytes = subsecond_cli_support::resolve_undefined( + let stub_bytes = subsecond_cli_support::resolve_undefined( &orig_exe, &object_files, &self.triple, aslr_reference.context("ASLR reference not found - is the client connected?")?, ) .expect("failed to resolve patch symbols"); - let patch_file = self.main_exe().with_file_name("patch-syms.o"); - std::fs::write(&patch_file, resolved_patch_bytes)?; + + // Currently we're dropping stub.o in the exe dir, but should probably just move to a tempfile? + let patch_file = self.main_exe().with_file_name("stub.o"); + std::fs::write(&patch_file, stub_bytes)?; object_files.push(patch_file); } let linker = match self.platform { Platform::Web => self.workspace.wasm_ld(), - Platform::Android => { - let tools = - crate::build::android_tools().context("Could not determine android tools")?; - tools.android_cc(&self.triple) - } + Platform::Android => android_tools() + .context("Could not determine android tools")? + .android_cc(&self.triple), // Note that I think rust uses rust-lld now, so we need to respect its argument profile // https://blog.rust-lang.org/2024/05/17/enabling-rust-lld-on-linux.html @@ -1074,10 +1167,8 @@ impl BuildRequest { | Platform::Ios | Platform::Linux | Platform::Server - | Platform::Liveview => PathBuf::from("cc"), - - // I think this is right?? does windows use cc? - Platform::Windows => PathBuf::from("cc"), + | Platform::Liveview + | Platform::Windows => PathBuf::from("cc"), }; let thin_args = self.thin_link_args(&args)?; @@ -1263,7 +1354,7 @@ impl BuildRequest { cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); cmd.env_remove("RUSTC_WRAPPER"); cmd.env_remove(DX_RUSTC_WRAPPER_ENV_VAR); - cmd.envs(self.env_vars(ctx)?); + cmd.envs(self.cargo_build_env_vars(ctx)?); cmd.arg(format!( "-Clinker={}", dunce::canonicalize(std::env::current_exe().unwrap()) @@ -1290,8 +1381,8 @@ impl BuildRequest { .current_dir(self.crate_dir()) .arg("--message-format") .arg("json-diagnostic-rendered-ansi") - .args(self.build_arguments(ctx)) - .envs(self.env_vars(ctx)?); + .args(self.cargo_build_arguments(ctx)) + .envs(self.cargo_build_env_vars(ctx)?); if ctx.mode == BuildMode::Fat { cmd.env( @@ -1316,7 +1407,10 @@ impl BuildRequest { } /// Create a list of arguments for cargo builds - fn build_arguments(&self, ctx: &BuildContext) -> Vec { + /// + /// We always use `cargo rustc` *or* `rustc` directly. This means we can pass extra flags like + /// `-C` arguments directly to the compiler.a + fn cargo_build_arguments(&self, ctx: &BuildContext) -> Vec { let mut cargo_args = Vec::new(); // Add required profile flags. --release overrides any custom profiles. @@ -1343,28 +1437,28 @@ impl BuildRequest { cargo_args.push(String::from("-p")); cargo_args.push(self.cargo_package.clone()); + // Set the executable match self.executable_type() { TargetKind::Bin => cargo_args.push("--bin".to_string()), TargetKind::Lib => cargo_args.push("--lib".to_string()), TargetKind::Example => cargo_args.push("--example".to_string()), _ => {} }; - cargo_args.push(self.executable_name().to_string()); - cargo_args.extend(self.cargo_args.clone()); - + // Merge in extra args. Order shouldn't really matter. + cargo_args.extend(self.extra_cargo_args.clone()); cargo_args.push("--".to_string()); + cargo_args.extend(self.extra_rustc_args.clone()); - // the bundle splitter needs relocation data - // we'll trim these out if we don't need them during the bundling process - // todo(jon): for wasm binary patching we might want to leave these on all the time. + // The bundle splitter needs relocation data to create a call-graph. + // This will automatically be erased by wasm-opt during the optimization step. if self.platform == Platform::Web && self.wasm_split { cargo_args.push("-Clink-args=--emit-relocs".to_string()); } // dx *always* links android and thin builds - if self.platform == Platform::Android || matches!(ctx.mode, BuildMode::Thin { .. }) { + if self.custom_linker.is_some() || matches!(ctx.mode, BuildMode::Thin { .. }) { cargo_args.push(format!( "-Clinker={}", dunce::canonicalize(std::env::current_exe().unwrap()) @@ -1373,65 +1467,91 @@ impl BuildRequest { )); } - match ctx.mode { - BuildMode::Base => {} - BuildMode::Thin { .. } | BuildMode::Fat => { - // This prevents rust from passing -dead_strip to the linker - // todo: don't save temps here unless we become the linker for the base app - cargo_args.extend_from_slice(&[ - "-Csave-temps=true".to_string(), - "-Clink-dead-code".to_string(), - ]); - - match self.platform { - // if macos/ios, -Wl,-all_load is required for the linker to work correctly - // macos uses ld64 but through the `cc` interface.a - Platform::MacOS | Platform::Ios => { - cargo_args.push("-Clink-args=-Wl,-all_load".to_string()); - } - - Platform::Android => { - cargo_args.push("-Clink-args=-Wl,--whole-archive".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 + // pass for both fat and thin linking. + // + // We need save-temps and no-dead-strip in both cases though. When we run `cargo rustc` with + // these args, they will be captured and re-ran for the fast compiles in the future, so whatever + // we set here will be set for all future hot patches too. + if matches!(ctx.mode, BuildMode::Thin { .. } | BuildMode::Fat) { + // rustc gives us some portable flags required: + // - link-dead-code: prevents rust from passing -dead_strip to the linker since that's the default. + // - save-temps=true: keeps the incremental object files around, which we need for manually linking. + cargo_args.extend_from_slice(&[ + "-Csave-temps=true".to_string(), + "-Clink-dead-code".to_string(), + ]); + + // We need to set some extra args that ensure all symbols make it into the final output + // and that the linker doesn't strip them out. + // + // This basically amounts of -all_load or --whole-archive, depending on the linker. + // We just assume an ld-like interface on macos and a gnu-ld interface elsewhere. + match self.triple.operating_system { + // macOS/iOS use ld64 but through the `cc` interface. + OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) => { + cargo_args.push("-Clink-args=-Wl,-all_load".to_string()); + } - // if linux -Wl,--whole-archive is required for the linker to work correctly - Platform::Linux => { - cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); - } + // Linux and Android fit under this umbrella, both with the same clang-like entrypoint + // and the gnu-ld interface. + OperatingSystem::Linux => { + cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); + } - // if windows -Wl,--whole-archive is required for the linker to work correctly - // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170 - Platform::Windows => { - cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); - } + // If windows -Wl,--whole-archive is required since it follows gnu-ld convention. + // There might be other flags on windows - we haven't tested windows thoroughly. + // + // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170 + OperatingSystem::Windows => { + cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); + } - // if web, -Wl,--whole-archive is required for the linker to work correctly. - // We also use --no-gc-sections and --export-table and --export-memory to push - // said symbols into the export table. - // - // We use --emit-relocs but scrub those before they make it into the final output. - // This is designed for us to build a solid call graph. - // - // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld with a -target): - // /Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld - // /Users/jonkelley/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld - // - // export all should place things like env.memory into the export table so we can access them - // when loading the patches - Platform::Web => { - cargo_args.push("-Clink-arg=--no-gc-sections".into()); - cargo_args.push("-Clink-arg=--growable-table".into()); - cargo_args.push("-Clink-arg=--whole-archive".into()); - cargo_args.push("-Clink-arg=--export-table".into()); - cargo_args.push("-Clink-arg=--export-memory".into()); - cargo_args.push("-Clink-arg=--emit-relocs".into()); - cargo_args.push("-Clink-arg=--export=__stack_pointer".into()); - cargo_args.push("-Clink-arg=--export=__heap_base".into()); - cargo_args.push("-Clink-arg=--export=__data_end".into()); - cargo_args.push("-Crelocation-model=pic".into()); - } + // if web, -Wl,--whole-archive is required since it follows gnu-ld convention. + // + // We also use --no-gc-sections and --export-table and --export-memory to push + // said symbols into the export table. + // + // We use --emit-relocs to build up a solid call graph. + // + // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld with a `-target wasm` flag): + // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld + // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld + // + // Note that we can't use --export-all, unfortunately, since some symbols are internal + // to wasm-bindgen and exporting them causes the JS generation to fail. + // + // We are basically replicating what emscripten does here with its dynamic linking + // approach where the MAIN_MODULE is very "fat" and exports the necessary arguments + // for the side modules to be linked in. This guide is really helpful: + // + // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md + // + // The trickiest one here is -Crelocation-model=pic, which forces data symbols + // into a GOT, making it possible to import them from the main module. + // + // 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::Wasi | OperatingSystem::Unknown + if self.platform == Platform::Web => + { + cargo_args.push("-Clink-arg=--no-gc-sections".into()); + cargo_args.push("-Clink-arg=--growable-table".into()); + cargo_args.push("-Clink-arg=--whole-archive".into()); + cargo_args.push("-Clink-arg=--export-table".into()); + cargo_args.push("-Clink-arg=--export-memory".into()); + cargo_args.push("-Clink-arg=--emit-relocs".into()); + cargo_args.push("-Clink-arg=--export=__stack_pointer".into()); + cargo_args.push("-Clink-arg=--export=__heap_base".into()); + cargo_args.push("-Clink-arg=--export=__data_end".into()); + cargo_args.push("-Crelocation-model=pic".into()); + } - _ => {} + _ => { + tracing::error!("Thin linking is not supported on this platform - hot patching might not work properly."); } } } @@ -1439,56 +1559,7 @@ impl BuildRequest { cargo_args } - /// Try to get the unit graph for the crate. This is a nightly only feature which may not be available with the current version of rustc the user has installed. - async fn get_unit_count(&self, ctx: &BuildContext) -> crate::Result { - #[derive(Debug, Deserialize)] - struct UnitGraph { - units: Vec, - } - - let output = tokio::process::Command::new("cargo") - .arg("+nightly") - .arg("build") - .arg("--unit-graph") - .arg("-Z") - .arg("unstable-options") - .args(self.build_arguments(ctx)) - .envs(self.env_vars(ctx)?) - .output() - .await?; - - if !output.status.success() { - return Err(anyhow::anyhow!("Failed to get unit count").into()); - } - - let output_text = String::from_utf8(output.stdout).context("Failed to get unit count")?; - let graph: UnitGraph = - serde_json::from_str(&output_text).context("Failed to get unit count")?; - - Ok(graph.units.len()) - } - - /// Get an estimate of the number of units in the crate. If nightly rustc is not available, this will return an estimate of the number of units in the crate based on cargo metadata. - /// TODO: always use https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph once it is stable - async fn get_unit_count_estimate(&self, ctx: &BuildContext) -> usize { - // Try to get it from nightly - if let Ok(count) = self.get_unit_count(ctx).await { - return count; - } - - // Otherwise, use cargo metadata - let units = self - .workspace - .krates - .krates_filtered(krates::DepKind::Dev) - .iter() - .map(|k| k.targets.len()) - .sum::(); - - (units as f64 / 3.5) as usize - } - - fn env_vars(&self, ctx: &BuildContext) -> Result> { + fn cargo_build_env_vars(&self, ctx: &BuildContext) -> Result> { let mut env_vars = vec![]; // Make sure to set all the crazy android flags. Cross-compiling is hard, man. @@ -1497,9 +1568,7 @@ impl BuildRequest { }; // If we're either zero-linking or using a custom linker, make `dx` itself do the linking. - if matches!(ctx.mode, BuildMode::Fat | BuildMode::Thin { .. }) - | self.custom_linker.is_some() - { + if self.custom_linker.is_some() || matches!(ctx.mode, BuildMode::Thin { .. }) { env_vars.push(( LinkAction::ENV_VAR_NAME, LinkAction { @@ -1533,7 +1602,7 @@ impl BuildRequest { env_vars: &mut Vec<(&str, String)>, rustf_flags: bool, ) -> Result<()> { - let tools = crate::build::android_tools().context("Could not determine android tools")?; + let tools = android_tools().context("Could not determine android tools")?; let linker = tools.android_cc(&self.triple); let min_sdk_version = tools.min_sdk_version(); let ar_path = tools.ar_path(); @@ -1610,6 +1679,60 @@ impl BuildRequest { Ok(()) } + /// Get an estimate of the number of units in the crate. If nightly rustc is not available, this + /// will return an estimate of the number of units in the crate based on cargo metadata. + /// + /// TODO: always use https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph once it is stable + async fn get_unit_count_estimate(&self, ctx: &BuildContext) -> usize { + // Try to get it from nightly + if let Ok(count) = self.get_unit_count(ctx).await { + return count; + } + + // Otherwise, use cargo metadata + let units = self + .workspace + .krates + .krates_filtered(krates::DepKind::Dev) + .iter() + .map(|k| k.targets.len()) + .sum::(); + + (units as f64 / 3.5) as usize + } + + /// Try to get the unit graph for the crate. This is a nightly only feature which may not be + /// available with the current version of rustc the user has installed. + /// + /// It also might not be super reliable - I think in practice it occasionally returns 2x the units. + async fn get_unit_count(&self, ctx: &BuildContext) -> crate::Result { + #[derive(Debug, Deserialize)] + struct UnitGraph { + units: Vec, + } + + let output = tokio::process::Command::new("cargo") + .arg("+nightly") + .arg("build") + .arg("--unit-graph") + .arg("-Z") + .arg("unstable-options") + .args(self.cargo_build_arguments(ctx)) + .envs(self.cargo_build_env_vars(ctx)?) + .output() + .await?; + + if !output.status.success() { + return Err(anyhow::anyhow!("Failed to get unit count").into()); + } + + let output_text = String::from_utf8(output.stdout).context("Failed to get unit count")?; + let graph: UnitGraph = + serde_json::from_str(&output_text).context("Failed to get unit count")?; + + Ok(graph.units.len()) + } + pub(crate) fn all_target_features(&self) -> Vec { let mut features = self.features.clone(); @@ -2988,7 +3111,7 @@ impl BuildRequest { /// will do its best to fill in the missing bits by exploring the sdk structure /// IE will attempt to use the Java installed from android studio if possible. async fn verify_android_tooling(&self) -> Result<()> { - let android = crate::build::android_tools().context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")?; + let android = android_tools().context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")?; let linker = android.android_cc(&self.triple); diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 756ff3ed47..7a1e803f63 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -56,13 +56,25 @@ pub(crate) struct BuildArgs { #[clap(long)] pub(crate) target: Option, - // todo -- make a subcommand called "--" that takes all the remaining args - /// Extra arguments passed to `rustc` + /// Extra arguments passed to `cargo` /// - /// cargo rustc -- -Clinker + /// To see a list of args, run `cargo rustc --help` + /// + /// This can include stuff like, "--locked", "--frozen", etc. Note that `dx` sets many of these + /// args directly from other args in this command. #[clap(value_delimiter = ',')] pub(crate) cargo_args: Vec, + /// Extra arguments passed to `rustc`. This can be used to customize the linker, or other flags. + /// + /// For example, specifign `dx build --rustc-args "-Clink-arg=-Wl,-blah"` will pass "-Clink-arg=-Wl,-blah" + /// to the underlying the `cargo rustc` command: + /// + /// cargo rustc -- -Clink-arg=-Wl,-blah + /// + #[clap(value_delimiter = ' ')] + pub(crate) rustc_args: Vec, + /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] #[clap(long)] #[serde(default)] From 96d07adf871af4219a954f6af118d72676a50101 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 14:15:39 -0700 Subject: [PATCH 137/301] ton more docs --- packages/cli/src/build/context.rs | 14 +- packages/cli/src/build/request.rs | 336 +++++++++++++++++++---------- packages/cli/src/cli/build.rs | 5 +- packages/cli/src/cli/bundle.rs | 6 +- packages/cli/src/cli/run.rs | 6 +- packages/cli/src/serve/runner.rs | 4 +- packages/dx-wire-format/src/lib.rs | 1 + 7 files changed, 247 insertions(+), 125 deletions(-) diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index aa3b8589bc..49419d79b0 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -65,7 +65,7 @@ pub enum BuildUpdate { impl BuildContext { pub(crate) fn status_wasm_bindgen_start(&self) { _ = self.tx.unbounded_send(BuildUpdate::Progress { - stage: BuildStage::RunningBindgen {}, + stage: BuildStage::RunningBindgen, }); } @@ -77,7 +77,7 @@ impl BuildContext { pub(crate) fn status_start_bundle(&self) { _ = self.tx.unbounded_send(BuildUpdate::Progress { - stage: BuildStage::Bundling {}, + stage: BuildStage::Bundling, }); } @@ -137,13 +137,19 @@ impl BuildContext { pub(crate) fn status_optimizing_wasm(&self) { _ = self.tx.unbounded_send(BuildUpdate::Progress { - stage: BuildStage::OptimizingWasm {}, + stage: BuildStage::OptimizingWasm, + }); + } + + pub(crate) fn status_hotpatching(&self) { + _ = self.tx.unbounded_send(BuildUpdate::Progress { + stage: BuildStage::Hotpatching, }); } pub(crate) fn status_installing_tooling(&self) { _ = self.tx.unbounded_send(BuildUpdate::Progress { - stage: BuildStage::InstallingTooling {}, + stage: BuildStage::InstallingTooling, }); } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 93a6450a23..bca694b9af 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1,8 +1,92 @@ -//! ## Web: +//! # BuildRequest - the core of the build process +//! +//! The BuildRequest object is the core of the build process. It contains all the resolved arguments +//! flowing in from the CLI, dioxus.toml, env vars, and the workspace. +//! +//! Every BuildRequest is tied to a given workspace and BuildArgs. For simplicity's sake, the BuildArgs +//! struct is used to represent the CLI arguments and all other configuration is basically just +//! extra CLI arguments, but in a configuration format. +//! +//! When [`BuildRequest::build`] is called, it will prepare its work directory in the target folder +//! and then start running the build process. A [`BuildContext`] is required to customize this +//! build process, containing a channel for progress updates and the build mode. +//! +//! The [`BuildMode`] is extremely important since it influences how the build is performed. Most +//! "normal" builds just use [`BuildMode::Base`], but we also support [`BuildMode::Fat`] and +//! [`BuildMode::Thin`]. These builds are used together to power the hot-patching and fast-linking +//! engine. +//! +//! The BuildRequest is also responsible for writing the final build artifacts to disk. This includes +//! +//! - Writing the executable +//! - Processing assets from the artifact +//! - Writing any metadata or configuration files (Info.plist, AndroidManifest.xml) +//! - Bundle splitting (for wasm) and wasm-bindgen +//! +//! In some cases, the BuildRequest also handles the linking of the final executable. Specifically, +//! - For Android, we use `dx` as an opaque linker to dynamically find the true android linker +//! - For hotpatching, the CLI manually links the final executable with a stub file +//! +//! ## Build formats: +//! +//! We support building for the most popular platforms: +//! - Web via wasm-bindgen +//! - macOS via app-bundle +//! - iOS via app-bundle +//! - Android via gradle +//! - Linux via app-image +//! - Windows via exe, msi/msix +//! +//! Note that we are missing some setups that we *should* support: +//! - PWAs, WebWorkers, ServiceWorkers +//! - Web Extensions +//! - Linux via flatpak/snap +//! +//! There are some less popular formats that we might want to support eventually: +//! - TVOS, watchOS +//! - OpenHarmony +//! +//! Also, some deploy platforms have their own bespoke formats: +//! - Cloudflare workers +//! - AWS Lambda +//! +//! Currently, we defer most of our deploy-based bundling to Tauri bundle, though we should migrate +//! to just bundling everything ourselves. This would require us to implement code-signing which +//! is a bit of a pain, but fortunately a solved process (https://github.com/rust-mobile/xbuild). +//! +//! ## Build Structure +//! +//! Builds generally follow the same structure everywhere: +//! - A main executable +//! - Sidecars (alternate entrypoints, framewrok plugins, etc) +//! - Assets (images, fonts, etc) +//! - Metadata (Info.plist, AndroidManifest.xml) +//! - Glue code (java, kotlin, javascript etc) +//! - Entitlements for code-signing and verification +//! +//! We need to be careful to not try and put a "round peg in a square hole," but most platforms follow +//! the same pattern. +//! +//! As such, we try to assemble a build directory that's somewhat sensible: +//! - A main "staging" dir for a given app +//! - Per-profile dirs (debug/release) +//! - A platform dir (ie web/desktop/android/ios) +//! - The "bundle" dir which is basically the `.app` format or `wwww` dir. +//! - The "executable" dir where the main exe is housed +//! - The "assets" dir where the assets are housed +//! - The "meta" dir where stuff like Info.plist, AndroidManifest.xml, etc are housed +//! +//! There's also some "quirky" folders that need to be stable between builds but don't influence the +//! bundle itself: +//! - session_cache_dir which stores stuff like window position +//! +//! ### Web: +//! //! Create a folder that is somewhat similar to an app-image (exe + asset) //! The server is dropped into the `web` folder, even if there's no `public` folder. //! If there's no server (SPA), we still use the `web` folder, but it only contains the //! public folder. +//! //! ``` //! web/ //! server @@ -18,7 +102,8 @@ //! logo.png //! ``` //! -//! ## Linux: +//! ### Linux: +//! //! https://docs.appimage.org/reference/appdir.html#ref-appdir //! current_exe.join("Assets") //! ``` @@ -30,7 +115,8 @@ //! logo.png //! ``` //! -//! ## Macos +//! ### Macos +//! //! We simply use the macos format where binaries are in `Contents/MacOS` and assets are in `Contents/Resources` //! We put assets in an assets dir such that it generally matches every other platform and we can //! output `/assets/blah` from manganis. @@ -48,7 +134,8 @@ //! _CodeSignature/ //! ``` //! -//! ## iOS +//! ### iOS +//! //! Not the same as mac! ios apps are a bit "flattened" in comparison. simpler format, presumably //! since most ios apps don't ship frameworks/plugins and such. //! @@ -59,7 +146,7 @@ //! assets/ //! ``` //! -//! ## Android: +//! ### Android: //! //! Currently we need to generate a `src` type structure, not a pre-packaged apk structure, since //! we need to compile kotlin and java. This pushes us into using gradle and following a structure @@ -115,7 +202,7 @@ //! ``` //! Notice that we *could* feasibly build this ourselves :) //! -//! ## Windows: +//! ### Windows: //! https://superuser.com/questions/749447/creating-a-single-file-executable-from-a-directory-in-windows //! Windows does not provide an AppImage format, so instead we're going build the same folder //! structure as an AppImage, but when distributing, we'll create a .exe that embeds the resources @@ -157,7 +244,6 @@ //! root().join(bundled) //! ``` //! -//! //! Every dioxus app can have an optional server executable which will influence the final bundle. //! This is built in parallel with the app executable during the `build` phase and the progres/status //! of the build is aggregated. @@ -219,7 +305,6 @@ //! //! The idea here is that we can run any of the programs in the same way that they're deployed. //! -//! //! ## Bundle structure links //! - apple: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle //! - appimage: https://docs.appimage.org/packaging-guide/manual.html#ref-manual @@ -241,7 +326,7 @@ use itertools::Itertools; use krates::{cm::TargetKind, KrateDetails, Krates, NodeId, Utf8PathBuf}; use manganis::{AssetOptions, JsAssetOptions}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, future::Future, @@ -408,9 +493,7 @@ impl BuildRequest { /// /// Note: Build requests are typically created only when the CLI is invoked or when significant /// changes are detected in the `Cargo.toml` (e.g., features added or removed). - pub(crate) async fn new(args: &BuildArgs) -> Result { - let workspace = Workspace::current().await?; - + pub(crate) async fn new(args: &BuildArgs, workspace: Arc) -> Result { let crate_package = workspace.find_main_package(args.package.clone())?; let config = workspace @@ -518,7 +601,7 @@ impl BuildRequest { }; // Determine the --package we'll pass to cargo. - // todo: I think this might be wrong - we don't want to use main_package necessarily...a + // todo: I think this might be wrong - we don't want to use main_package necessarily... let package = args .package .clone() @@ -842,6 +925,7 @@ impl BuildRequest { // web/ // bundle/ // build/ + // server.exe // public/ // index.html // wasm/ @@ -876,6 +960,7 @@ impl BuildRequest { | Platform::Ios | Platform::Liveview | Platform::Server => { + // We wipe away the dir completely, which is not great behavior :/ _ = std::fs::remove_dir_all(self.exe_dir()); std::fs::create_dir_all(self.exe_dir())?; std::fs::copy(&exe, self.main_exe())?; @@ -982,6 +1067,7 @@ impl BuildRequest { // Parallel Copy over the assets and keep track of progress with an atomic counter let progress = ctx.tx.clone(); let ws_dir = self.workspace_dir(); + // Optimizing assets is expensive and blocking, so we do it in a tokio spawn blocking task tokio::task::spawn_blocking(move || { assets_to_transfer @@ -1062,17 +1148,16 @@ impl BuildRequest { /// We also run some post processing steps here, like extracting out any new assets. async fn write_patch( &self, - _ctx: &BuildContext, + ctx: &BuildContext, aslr_reference: Option, artifacts: &mut BuildArtifacts, ) -> Result<()> { tracing::debug!("Patching existing bundle"); + ctx.status_hotpatching(); - let time_start = artifacts.time_start; let raw_args = std::fs::read_to_string(&self.link_args_file()) .context("Failed to read link args from file")?; let args = raw_args.lines().collect::>(); - let orig_exe = self.main_exe(); // Extract out the incremental object files. // @@ -1092,6 +1177,9 @@ impl BuildRequest { // Many args are passed twice, too, which can be confusing, but generally don't have any real // effect. Note that on macos/ios, there's a special macho header that *needs* to be set. // + // Also, some flags in darwin land might become deprecated, need to be super conservative: + // - https://developer.apple.com/forums/thread/773907 + // // ``` // cc // /dioxus/target/debug/subsecond-cli @@ -1128,13 +1216,14 @@ impl BuildRequest { .map(|arg| PathBuf::from(arg)) .collect::>(); - // Our wasm approach is quite specific to wasm. We don't need to resolve any missing symbols - // there since wasm is relocatable, but there is considerable pre and post processing work to get it - // working. - // // On non-wasm platforms, we generate a special shim object file which converts symbols from // fat binary into direct addresses from the running process. // + // Our wasm approach is quite specific to wasm. We don't need to resolve any missing symbols + // there since wasm is relocatable, but there is considerable pre and post processing work to + // satisfy undefined symbols that we do by munging the binary directly. + // + // todo: can we adjust our wasm approach to also use a similar system? // todo: don't require the aslr reference and just patch the got when loading. // // Requiring the ASLR offset here is necessary but unfortunately might be flakey in practice. @@ -1142,7 +1231,7 @@ impl BuildRequest { // making this hotpatch a failure. if self.platform != Platform::Web { let stub_bytes = subsecond_cli_support::resolve_undefined( - &orig_exe, + &self.main_exe(), &object_files, &self.triple, aslr_reference.context("ASLR reference not found - is the client connected?")?, @@ -1155,14 +1244,29 @@ impl BuildRequest { object_files.push(patch_file); } - let linker = match self.platform { + let cc = match self.platform { + // todo: we're using wasm-ld directly, but I think we can drive it with rust-lld and -flavor wasm Platform::Web => self.workspace.wasm_ld(), + + // The android clang linker is *special* and has some android-specific flags that we need + // + // Note that this is *clang*, not `lld`. Platform::Android => android_tools() .context("Could not determine android tools")? .android_cc(&self.triple), - // Note that I think rust uses rust-lld now, so we need to respect its argument profile + // 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 + // it requires msvc or gnu downloaded, which is a requirement to use rust anyways. + // + // The default linker might actually be slow though, so we could consider using lld or rust-lld + // since those are shipping by default on linux as of 1.86. Window's linker is the really slow one. + // // https://blog.rust-lang.org/2024/05/17/enabling-rust-lld-on-linux.html + // + // Note that "cc" is *not* a linker. It's a compiler! The arguments we pass need to be in + // the form of `-Wl,` for them to make it to the linker. This matches how rust does it + // which is confusing. Platform::MacOS | Platform::Ios | Platform::Linux @@ -1171,61 +1275,65 @@ impl BuildRequest { | Platform::Windows => PathBuf::from("cc"), }; - let thin_args = self.thin_link_args(&args)?; - - // todo: we should throw out symbols that we don't need and/or assemble them manually - // also we should make sure to propagate the right arguments (target, sysroot, etc) + // Run the linker directly! // - // also, https://developer.apple.com/forums/thread/773907 - // -undefined,dynamic_lookup is deprecated for ios but supposedly cpython is using it - // we might need to link a new patch file that implements the lookups - let res = Command::new(linker) + // 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(cc) .args(object_files.iter()) - .args(thin_args) + .args(self.thin_link_args(&args)?) .arg("-o") - .arg(&self.patch_exe(time_start)) + .arg(&self.patch_exe(artifacts.time_start)) .output() .await?; if !res.stderr.is_empty() { let errs = String::from_utf8_lossy(&res.stderr); - if !self.patch_exe(time_start).exists() { + if !self.patch_exe(artifacts.time_start).exists() { tracing::error!("Failed to generate patch: {}", errs.trim()); } else { tracing::debug!("Warnings during thin linking: {}", errs.trim()); } } + // For some really weird reason that I think is because of dlopen caching, future loads of the + // jump library will fail if we don't remove the original fat file. I think this could be + // because of library versioning and namespaces, but really unsure. + // + // The errors if you forget to do this are *extremely* cryptic - missing symbols that never existed. + // + // Fortunately, this binary exists in two places - the deps dir and the target out dir. We + // can just remove the one in the deps dir and the problem goes away. + if let Some(idx) = args.iter().position(|arg| *arg == "-o") { + _ = std::fs::remove_file(&PathBuf::from(args[idx + 1])); + } + + // Also clean up the temp artifacts // // Clean up the temps manually // // todo: we might want to keep them around for debugging purposes // for file in object_files { // _ = std::fs::remove_file(file); // } - // Also clean up the original fat file since that's causing issues with rtld_global - // todo: this might not be platform portable - let link_orig = args - .iter() - .position(|arg| *arg == "-o") - .expect("failed to find -o"); - let link_file: PathBuf = args[link_orig + 1].into(); - _ = std::fs::remove_file(&link_file); - Ok(()) } + /// Take the original args passed to the "fat" build and then create the "thin" variant. + /// + /// 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> { use target_lexicon::OperatingSystem; let triple = self.triple.clone(); - let mut args = vec![]; + let mut out_args = vec![]; tracing::trace!("original args:\n{}", original_args.join(" ")); match triple.operating_system { // wasm32-unknown-unknown -> use wasm-ld (gnu-lld) OperatingSystem::Unknown if self.platform == Platform::Web => { - args.extend([ + out_args.extend([ "--import-memory".to_string(), "--import-table".to_string(), "--growable-table".to_string(), @@ -1241,28 +1349,28 @@ impl BuildRequest { } // this uses "cc" and these args need to be ld compatible + // // aarch64-apple-ios // aarch64-apple-darwin OperatingSystem::IOS(_) | OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) => { - args.extend(["-Wl,-dylib".to_string()]); + out_args.extend(["-Wl,-dylib".to_string()]); match triple.architecture { target_lexicon::Architecture::Aarch64(_) => { - args.push("-arch".to_string()); - args.push("arm64".to_string()); + out_args.push("-arch".to_string()); + out_args.push("arm64".to_string()); } target_lexicon::Architecture::X86_64 => { - args.push("-arch".to_string()); - args.push("x86_64".to_string()); + out_args.push("-arch".to_string()); + out_args.push("x86_64".to_string()); } _ => {} } } - // android/linux - // need to be compatible with lld + // android/linux need to be compatible with lld OperatingSystem::Linux if triple.environment == Environment::Android => { - args.extend( + out_args.extend( [ "-shared".to_string(), "-Wl,--eh-frame-hdr".to_string(), @@ -1298,7 +1406,7 @@ impl BuildRequest { } OperatingSystem::Linux => { - args.extend([ + out_args.extend([ "-Wl,--eh-frame-hdr".to_string(), "-Wl,-z,noexecstack".to_string(), "-Wl,-z,relro,-z,now".to_string(), @@ -1320,18 +1428,25 @@ impl BuildRequest { }; if let Some(vale) = extract_value("-target") { - args.push("-target".to_string()); - args.push(vale); + out_args.push("-target".to_string()); + out_args.push(vale); } if let Some(vale) = extract_value("-isysroot") { - args.push("-isysroot".to_string()); - args.push(vale); + out_args.push("-isysroot".to_string()); + out_args.push(vale); } - Ok(args) + Ok(out_args) } + /// Assemble the `cargo rustc` / `rustc` command + /// + /// When building fat/base binaries, we use `cargo rustc`. + /// When building thin binaries, we use `rustc` directly. + /// + /// When processing the output of this command, you need to make sure to handle both cases which + /// both have different formats (but with json output for both). fn build_command(&self, ctx: &BuildContext) -> Result { match &ctx.mode { // We're assembling rustc directly, so we need to be *very* careful. Cargo sets rustc's @@ -1409,7 +1524,7 @@ impl BuildRequest { /// Create a list of arguments for cargo builds /// /// We always use `cargo rustc` *or* `rustc` directly. This means we can pass extra flags like - /// `-C` arguments directly to the compiler.a + /// `-C` arguments directly to the compiler. fn cargo_build_arguments(&self, ctx: &BuildContext) -> Vec { let mut cargo_args = Vec::new(); @@ -1559,12 +1674,12 @@ impl BuildRequest { cargo_args } - fn cargo_build_env_vars(&self, ctx: &BuildContext) -> Result> { + fn cargo_build_env_vars(&self, ctx: &BuildContext) -> Result> { let mut env_vars = vec![]; // Make sure to set all the crazy android flags. Cross-compiling is hard, man. if self.platform == Platform::Android { - self.build_android_env(&mut env_vars, true)?; + env_vars.extend(self.android_env_vars()?); }; // If we're either zero-linking or using a custom linker, make `dx` itself do the linking. @@ -1597,11 +1712,9 @@ impl BuildRequest { Ok(env_vars) } - fn build_android_env( - &self, - env_vars: &mut Vec<(&str, String)>, - rustf_flags: bool, - ) -> Result<()> { + fn android_env_vars(&self) -> Result> { + let mut env_vars = vec![]; + let tools = android_tools().context("Could not determine android tools")?; let linker = tools.android_cc(&self.triple); let min_sdk_version = tools.min_sdk_version(); @@ -1625,10 +1738,14 @@ impl BuildRequest { env_vars.push(("TARGET_CC", target_cc.display().to_string())); env_vars.push(("TARGET_CXX", target_cxx.display().to_string())); env_vars.push(("ANDROID_NDK_ROOT", ndk.display().to_string())); + if let Some(java_home) = java_home { tracing::debug!("Setting JAVA_HOME to {java_home:?}"); env_vars.push(("JAVA_HOME", 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", "dev.dioxus.main".to_string())); env_vars.push(("WRY_ANDROID_LIBRARY", "dioxusmain".to_string())); env_vars.push(( @@ -1638,23 +1755,17 @@ impl BuildRequest { .to_string(), )); - if rustf_flags { - env_vars.push(("RUSTFLAGS", { - let mut rust_flags = std::env::var("RUSTFLAGS").unwrap_or_default(); - - // todo(jon): maybe we can make the symbol aliasing logic here instead of using llvm-objcopy - if self.platform == Platform::Android { - let cur_exe = std::env::current_exe().unwrap(); - rust_flags.push_str(format!(" -Clinker={}", cur_exe.display()).as_str()); - rust_flags.push_str(" -Clink-arg=-landroid"); - rust_flags.push_str(" -Clink-arg=-llog"); - rust_flags.push_str(" -Clink-arg=-lOpenSLES"); - rust_flags.push_str(" -Clink-arg=-Wl,--export-dynamic"); - } - - rust_flags - })); - } + // Set the rust flags for android which get passed to *every* crate in the graph. + // todo: I don't think we should be passing --export-dynamic here, but it works. + // At least for production, we shouldn't. + env_vars.push(("RUSTFLAGS", { + let mut rust_flags = std::env::var("RUSTFLAGS").unwrap_or_default(); + rust_flags.push_str(" -Clink-arg=-landroid"); + rust_flags.push_str(" -Clink-arg=-llog"); + rust_flags.push_str(" -Clink-arg=-lOpenSLES"); + rust_flags.push_str(" -Clink-arg=-Wl,--export-dynamic"); + rust_flags + })); // 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. @@ -1676,7 +1787,7 @@ impl BuildRequest { // ); // env_vars.push(("PATH", extended_path)); - Ok(()) + Ok(env_vars) } /// Get an estimate of the number of units in the crate. If nightly rustc is not available, this @@ -1797,16 +1908,22 @@ impl BuildRequest { Platform::Windows => format!("{}.exe", self.executable_name()), // from the apk spec, the root exe is a shared library - // we include the user's rust code as a shared library with a fixed namespacea + // we include the user's rust code as a shared library with a fixed namespace Platform::Android => "libdioxusmain.so".to_string(), - Platform::Web => format!("{}_bg.wasm", self.executable_name()), // this will be wrong, I think, but not important? + // this will be wrong, I think, but not important? + Platform::Web => format!("{}_bg.wasm", self.executable_name()), // todo: maybe this should be called AppRun? Platform::Linux => self.executable_name().to_string(), } } + /// Assemble the android app dir. + /// + /// This is a bit of a mess since we need to create a lot of directories and files. Other approaches + /// would be to unpack some zip folder or something stored via `include_dir!()`. However, we do + /// need to customize the whole setup a bit, so it's just simpler (though messier) to do it this way. fn build_android_app_dir(&self) -> Result<()> { use std::fs::{create_dir_all, write}; let root = self.root_dir(); @@ -1814,7 +1931,6 @@ impl BuildRequest { // gradle let wrapper = root.join("gradle").join("wrapper"); create_dir_all(&wrapper)?; - tracing::debug!("Initialized Gradle wrapper: {:?}", wrapper); // app let app = root.join("app"); @@ -1829,20 +1945,26 @@ impl BuildRequest { create_dir_all(&app_jnilibs)?; create_dir_all(&app_assets)?; create_dir_all(&app_kotlin_out)?; - tracing::debug!("Initialized app: {:?}", app); - tracing::debug!("Initialized app/src: {:?}", app_main); - tracing::debug!("Initialized app/src/kotlin: {:?}", app_kotlin); - tracing::debug!("Initialized app/src/jniLibs: {:?}", app_jnilibs); - tracing::debug!("Initialized app/src/assets: {:?}", app_assets); - tracing::debug!("Initialized app/src/kotlin/main: {:?}", app_kotlin_out); + + tracing::debug!( + r#"Initialized android dirs: +- gradle: {wrapper:?} +- app/ {app:?} +- app/src: {app_main:?} +- app/src/kotlin: {app_kotlin:?} +- app/src/jniLibs: {app_jnilibs:?} +- app/src/assets: {app_assets:?} +- app/src/kotlin/main: {app_kotlin_out:?} +"# + ); // handlebars - #[derive(serde::Serialize)] - struct HbsTypes { + #[derive(Serialize)] + struct AndroidHandlebarsObjects { application_id: String, app_name: String, } - let hbs_data = HbsTypes { + let hbs_data = AndroidHandlebarsObjects { application_id: self.full_mobile_app_name(), app_name: self.bundled_app_name(), }; @@ -1910,7 +2032,7 @@ impl BuildRequest { )?, )?; - // Write the res folder + // Write the res folder, containing stuff like default icons, colors, and menubars. let res = app_main.join("res"); create_dir_all(&res)?; create_dir_all(res.join("values"))?; @@ -1990,7 +2112,7 @@ impl BuildRequest { Ok(()) } - pub(crate) fn wry_android_kotlin_files_out_dir(&self) -> PathBuf { + fn wry_android_kotlin_files_out_dir(&self) -> PathBuf { let mut kotlin_dir = self .root_dir() .join("app") @@ -2002,21 +2124,9 @@ impl BuildRequest { kotlin_dir = kotlin_dir.join(segment); } - tracing::debug!("app_kotlin_out: {:?}", kotlin_dir); - kotlin_dir } - // pub(crate) async fn new(args: &TargetArgs) -> Result { - - // Ok(Self { - // workspace: workspace.clone(), - // package, - // config: dioxus_config, - // target: Arc::new(target), - // }) - // } - /// The asset dir we used to support before manganis became the default. /// This generally was just a folder in your Dioxus.toml called "assets" or "public" where users /// would store their assets. @@ -2282,7 +2392,7 @@ impl BuildRequest { } /// Return the version of the wasm-bindgen crate if it exists - pub(crate) fn wasm_bindgen_version(&self) -> Option { + fn wasm_bindgen_version(&self) -> Option { self.workspace .krates .krates_by_name("wasm-bindgen") @@ -2755,7 +2865,7 @@ impl BuildRequest { } fn info_plist_contents(&self, platform: Platform) -> Result { - #[derive(serde::Serialize)] + #[derive(Serialize)] pub struct InfoPlistData { pub display_name: String, pub bundle_name: String, diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 7a1e803f63..091b789729 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,4 +1,4 @@ -use crate::{cli::*, AppBuilder, BuildRequest}; +use crate::{cli::*, AppBuilder, BuildRequest, Workspace}; use crate::{BuildMode, Platform}; use target_lexicon::Triple; @@ -116,7 +116,8 @@ impl BuildArgs { pub async fn build(self) -> Result { tracing::info!("Building project..."); - let build = BuildRequest::new(&self) + let workspace = Workspace::current().await?; + let build = BuildRequest::new(&self, workspace) .await .context("Failed to load Dioxus workspace")?; diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index ed6dbb2dda..b5ec121f17 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -1,4 +1,4 @@ -use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform}; +use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform, Workspace}; use anyhow::{anyhow, Context}; use dioxus_cli_config::{server_ip, server_port}; use futures_util::stream::FuturesUnordered; @@ -72,7 +72,9 @@ impl Bundle { // todo - maybe not? what if you want a devmode bundle? self.args.release = true; - let build = BuildRequest::new(&self.args) + let workspace = Workspace::current().await?; + + let build = BuildRequest::new(&self.args, workspace) .await .context("Failed to load Dioxus workspace")?; diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index 70fa6b517e..6ba26bfc9e 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform, Result}; +use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform, Result, Workspace}; /// Run the project with the given arguments #[derive(Clone, Debug, Parser)] @@ -11,7 +11,9 @@ pub(crate) struct RunArgs { impl RunArgs { pub(crate) async fn run(self) -> Result { - let build = BuildRequest::new(&self.build_args) + let workspace = Workspace::current().await?; + + let build = BuildRequest::new(&self.build_args, workspace) .await .context("error building project")?; diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index d9dd5de23b..a09ae5c694 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -140,7 +140,7 @@ impl AppRunner { // This involves modifying the BuildRequest to add the client features and server features // only if we can properly detect that it's a fullstack build. Careful with this, since // we didn't build BuildRequest to be generally mutable. - let client = BuildRequest::new(&args.build_arguments).await?; + let client = BuildRequest::new(&args.build_arguments, workspace.clone()).await?; let mut server = None; // Now we need to resolve the client features @@ -149,7 +149,7 @@ impl AppRunner { let mut build_args = args.build_arguments.clone(); build_args.platform = Some(Platform::Server); - let _server = BuildRequest::new(&build_args).await?; + let _server = BuildRequest::new(&build_args, workspace.clone()).await?; // ... todo: add the server features to the server build // ... todo: add the client features to the client build diff --git a/packages/dx-wire-format/src/lib.rs b/packages/dx-wire-format/src/lib.rs index 6cb6d98303..52288d745a 100644 --- a/packages/dx-wire-format/src/lib.rs +++ b/packages/dx-wire-format/src/lib.rs @@ -57,6 +57,7 @@ pub enum BuildStage { RunningBindgen, SplittingBundle, OptimizingWasm, + Hotpatching, CopyingAssets { current: usize, total: usize, From e6a84a887d8542c825d271b1a893342291fe578f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 15:53:54 -0700 Subject: [PATCH 138/301] clean up the cli a bit more --- Cargo.lock | 1 - examples/fullstack-hello-world/src/main.rs | 2 +- packages/cli/Cargo.toml | 1 - packages/cli/src/build/builder.rs | 42 ++++++------- packages/cli/src/build/context.rs | 36 +++++------ packages/cli/src/build/request.rs | 50 ++++++++------- packages/cli/src/build/tools.rs | 66 +++++--------------- packages/cli/src/cli/build.rs | 4 +- packages/cli/src/cli/config.rs | 5 +- packages/cli/src/cli/mod.rs | 2 +- packages/cli/src/main.rs | 1 - packages/cli/src/metadata.rs | 42 ------------- packages/cli/src/serve/mod.rs | 18 +++--- packages/cli/src/serve/output.rs | 16 ++--- packages/cli/src/serve/runner.rs | 4 +- packages/cli/src/serve/server.rs | 26 ++++---- packages/cli/src/serve/update.rs | 6 +- packages/cli/src/wasm_bindgen.rs | 3 +- packages/cli/src/workspace.rs | 71 +++++++++++++++++----- 19 files changed, 181 insertions(+), 215 deletions(-) delete mode 100644 packages/cli/src/metadata.rs diff --git a/Cargo.lock b/Cargo.lock index b4a35b4741..98c8a15f34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3646,7 +3646,6 @@ dependencies = [ "ansi-to-html", "ansi-to-tui", "anyhow", - "async-once-cell", "axum 0.8.3", "axum-extra", "axum-server", diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 84f1bc3d77..49781915ae 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -27,7 +27,7 @@ fn app() -> Element { #[server] async fn say_hi() -> Result { - Ok("DUAL asdasd ACHIEVED?????!".to_string()) + Ok("DUAL asdasd ACHIEVED?asdasdads????!".to_string()) } #[server] diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index b24cb747bc..d6c64aa036 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -128,7 +128,6 @@ dircpy = "0.3.19" plist = "1.7.0" memoize = "0.5.1" -async-once-cell = { workspace = true } depinfo = { workspace = true } [build-dependencies] diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index abb3dac51c..d81c403a99 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1,5 +1,5 @@ use crate::{ - BuildArgs, BuildArtifacts, BuildRequest, BuildStage, BuildUpdate, Platform, ProgressRx, + BuildArgs, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, ProgressRx, ProgressTx, Result, StructuredOutput, }; use anyhow::Context; @@ -156,9 +156,9 @@ impl AppBuilder { } /// Wait for any new updates to the builder - either it completed or gave us a message etc - pub(crate) async fn wait(&mut self) -> BuildUpdate { + pub(crate) async fn wait(&mut self) -> BuilderUpdate { use futures_util::StreamExt; - use BuildUpdate::*; + use BuilderUpdate::*; // Wait for the build to finish or for it to emit a status message let update = tokio::select! { @@ -167,9 +167,9 @@ impl AppBuilder { // Replace the build with an infinitely pending task so we can select it again without worrying about deadlocks/spins self.build_task = tokio::task::spawn(std::future::pending()); match bundle { - Ok(Ok(bundle)) => BuildUpdate::BuildReady { bundle }, - Ok(Err(err)) => BuildUpdate::BuildFailed { err }, - Err(err) => BuildUpdate::BuildFailed { err: crate::Error::Runtime(format!("Build panicked! {:?}", err)) }, + Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle }, + Ok(Err(err)) => BuilderUpdate::BuildFailed { err }, + Err(err) => BuilderUpdate::BuildFailed { err: crate::Error::Runtime(format!("Build panicked! {:?}", err)) }, } }, Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => { @@ -194,7 +194,7 @@ impl AppBuilder { // doing so will cause the changes to be lost since this wait call is called under a cancellable task // todo - move this handling to a separate function that won't be cancelled match &update { - BuildUpdate::Progress { stage } => { + BuilderUpdate::Progress { stage } => { // Prevent updates from flowing in after the build has already finished if !self.is_finished() { self.stage = stage.clone(); @@ -244,8 +244,8 @@ impl AppBuilder { } } } - BuildUpdate::CompilerMessage { .. } => {} - BuildUpdate::BuildReady { .. } => { + BuilderUpdate::CompilerMessage { .. } => {} + BuilderUpdate::BuildReady { .. } => { self.compiled_crates = self.expected_crates; self.bundling_progress = 1.0; self.stage = BuildStage::Success; @@ -253,7 +253,7 @@ impl AppBuilder { self.complete_compile(); self.bundle_end = Some(Instant::now()); } - BuildUpdate::BuildFailed { .. } => { + BuilderUpdate::BuildFailed { .. } => { tracing::debug!("Setting builder to failed state"); self.stage = BuildStage::Failed; } @@ -321,7 +321,7 @@ impl AppBuilder { pub(crate) async fn finish_build(&mut self) -> Result { loop { match self.wait().await { - BuildUpdate::Progress { stage } => { + BuilderUpdate::Progress { stage } => { match &stage { BuildStage::Compiling { current, @@ -345,19 +345,19 @@ impl AppBuilder { tracing::info!(json = ?StructuredOutput::BuildUpdate { stage: stage.clone() }); } - BuildUpdate::CompilerMessage { message } => { + BuilderUpdate::CompilerMessage { message } => { tracing::info!(json = ?StructuredOutput::CargoOutput { message: message.clone() }, %message); } - BuildUpdate::BuildReady { bundle } => { + BuilderUpdate::BuildReady { bundle } => { tracing::debug!(json = ?StructuredOutput::BuildFinished { path: self.build.root_dir(), }); return Ok(bundle); } - BuildUpdate::BuildFailed { err } => { + BuilderUpdate::BuildFailed { err } => { // Flush remaining compiler messages while let Ok(Some(msg)) = self.rx.try_next() { - if let BuildUpdate::CompilerMessage { message } = msg { + if let BuilderUpdate::CompilerMessage { message } = msg { tracing::info!(json = ?StructuredOutput::CargoOutput { message: message.clone() }, %message); } } @@ -365,9 +365,9 @@ impl AppBuilder { tracing::error!(?err, json = ?StructuredOutput::Error { message: err.to_string() }); return Err(err); } - BuildUpdate::StdoutReceived { msg } => {} - BuildUpdate::StderrReceived { msg } => {} - BuildUpdate::ProcessExited { status } => {} + BuilderUpdate::StdoutReceived { msg } => {} + BuilderUpdate::StderrReceived { msg } => {} + BuilderUpdate::ProcessExited { status } => {} } } } @@ -461,7 +461,6 @@ impl AppBuilder { } /// Gracefully kill the process and all of its children - /// /// Uses the `SIGTERM` signal on unix and `taskkill` on windows. /// This complex logic is necessary for things like window state preservation to work properly. @@ -578,7 +577,8 @@ impl AppBuilder { ) -> Result { let target = dioxus_cli_config::android_session_cache_dir().join(bundled_name); tracing::debug!("Pushing asset to device: {target:?}"); - let res = tokio::process::Command::new(crate::build::android_tools().unwrap().adb) + + let res = tokio::process::Command::new(&crate::build::android_tools().unwrap().adb) .arg("push") .arg(&changed_file) .arg(&target) @@ -1014,7 +1014,7 @@ We checked the folder: {} // Start backgrounded since .open() is called while in the arm of the top-level match tokio::task::spawn(async move { - let adb = crate::build::android_tools().unwrap().adb; + let adb = &crate::build::android_tools().unwrap().adb; // call `adb root` so we can push patches to the device if root { diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index 49419d79b0..9b6887a890 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -1,11 +1,11 @@ //! Report progress about the build to the user. We use channels to report progress back to the CLI. + +use super::BuildMode; use crate::{BuildArtifacts, BuildStage, Error, TraceSrc}; use cargo_metadata::CompilerMessage; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::{path::PathBuf, process::ExitStatus}; -use super::BuildMode; - /// The context of the build process. While the BuildRequest is a "plan" for the build, the BuildContext /// provides some dynamic configuration that is only known at runtime. For example, the Progress channel /// and the BuildMode can change while serving. @@ -17,14 +17,14 @@ pub struct BuildContext { pub mode: BuildMode, } -pub type ProgressTx = UnboundedSender; -pub type ProgressRx = UnboundedReceiver; +pub type ProgressTx = UnboundedSender; +pub type ProgressRx = UnboundedReceiver; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuildId(pub usize); #[allow(clippy::large_enum_variant)] -pub enum BuildUpdate { +pub enum BuilderUpdate { Progress { stage: BuildStage, }, @@ -64,25 +64,25 @@ pub enum BuildUpdate { impl BuildContext { pub(crate) fn status_wasm_bindgen_start(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::RunningBindgen, }); } pub(crate) fn status_splitting_bundle(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::SplittingBundle, }); } pub(crate) fn status_start_bundle(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::Bundling, }); } pub(crate) fn status_running_gradle(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::RunningGradle, }) } @@ -90,7 +90,7 @@ impl BuildContext { pub(crate) fn status_build_diagnostic(&self, message: CompilerMessage) { _ = self .tx - .unbounded_send(BuildUpdate::CompilerMessage { message }); + .unbounded_send(BuilderUpdate::CompilerMessage { message }); } pub(crate) fn status_build_error(&self, line: String) { @@ -102,7 +102,7 @@ impl BuildContext { } pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::Compiling { current: count, total, @@ -112,7 +112,7 @@ impl BuildContext { } pub(crate) fn status_starting_build(&self, crate_count: usize) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::Starting { patch: matches!(self.mode, BuildMode::Thin { .. }), crate_count, @@ -121,12 +121,12 @@ impl BuildContext { } pub(crate) fn status_copied_asset( - progress: &UnboundedSender, + progress: &UnboundedSender, current: usize, total: usize, path: PathBuf, ) { - _ = progress.unbounded_send(BuildUpdate::Progress { + _ = progress.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::CopyingAssets { current, total, @@ -136,25 +136,25 @@ impl BuildContext { } pub(crate) fn status_optimizing_wasm(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::OptimizingWasm, }); } pub(crate) fn status_hotpatching(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::Hotpatching, }); } pub(crate) fn status_installing_tooling(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::InstallingTooling, }); } pub(crate) fn status_compressing_assets(&self) { - _ = self.tx.unbounded_send(BuildUpdate::Progress { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::CompressingAssets, }); } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index bca694b9af..b2a95deab9 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -645,7 +645,7 @@ impl BuildRequest { Platform::Android => { super::android_tools() .unwrap() - .autodetect_android_triple() + .autodetect_android_device_triple() .await } }, @@ -670,6 +670,9 @@ impl BuildRequest { .context("Failed to create temporary file for rustc wrapper args")?, ); + let extra_rustc_args = shell_words::split(&args.rustc_args.clone().unwrap_or_default()) + .context("Failed to parse rustc args")?; + Ok(Self { platform, features, @@ -686,7 +689,7 @@ impl BuildRequest { link_args_file, link_err_file, rustc_wrapper_args_file, - extra_rustc_args: args.rustc_args.clone(), + extra_rustc_args, extra_cargo_args: args.cargo_args.clone(), nightly: args.nightly, cargo_package: package, @@ -1155,7 +1158,7 @@ impl BuildRequest { tracing::debug!("Patching existing bundle"); ctx.status_hotpatching(); - let raw_args = std::fs::read_to_string(&self.link_args_file()) + 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::>(); @@ -1168,20 +1171,22 @@ impl BuildRequest { // rely on them *too* much, but the *are* fundamental to how rust compiles your projects, and // linker interfaces probably won't change drastically for another 40 years. // - // We need to tear apart this command andonly pass the args that are relevant to our thin link. + // We need to tear apart this command and only pass the args that are relevant to our thin link. // Mainly, we don't want any rlibs to be linked. Occasionally some libraries like objc_exception // export a folder with their artifacts - unsure if we actually need to include them. Generally // you can err on the side that most *libraries* don't need to be linked here since dlopen - // satisfies those symbols anyways. + // satisfies those symbols anyways when the binary is loaded. // // Many args are passed twice, too, which can be confusing, but generally don't have any real - // effect. Note that on macos/ios, there's a special macho header that *needs* to be set. + // effect. Note that on macos/ios, there's a special macho header that needs to be set, otherwise + // dyld will complain.a // // Also, some flags in darwin land might become deprecated, need to be super conservative: // - https://developer.apple.com/forums/thread/773907 // + // The format of this command roughly follows: // ``` - // cc + // clang // /dioxus/target/debug/subsecond-cli // /var/folders/zs/gvrfkj8x33d39cvw2p06yc700000gn/T/rustcAqQ4p2/symbols.o // /dioxus/target/subsecond-dev/deps/subsecond_harness-acfb69cb29ffb8fa.05stnb4bovskp7a00wyyf7l9s.rcgu.o @@ -1332,13 +1337,19 @@ impl BuildRequest { match triple.operating_system { // wasm32-unknown-unknown -> use wasm-ld (gnu-lld) + // + // We need to import a few things - namely the memory and ifunc table. + // + // We can safely export everything, I believe, though that led to issues with the "fat" + // binaries that also might lead to issues here too. wasm-bindgen chokes on some symbols + // and the resulting JS has issues. + // + // We turn on both --pie and --experimental-pic but I think we only need --pie. OperatingSystem::Unknown if self.platform == Platform::Web => { out_args.extend([ "--import-memory".to_string(), "--import-table".to_string(), "--growable-table".to_string(), - "--export".to_string(), - "main".to_string(), "--export-all".to_string(), "--allow-undefined".to_string(), "--no-demangle".to_string(), @@ -1348,10 +1359,10 @@ impl BuildRequest { ]); } - // this uses "cc" and these args need to be ld compatible + // This uses "cc" and these args need to be ld compatible // - // aarch64-apple-ios - // aarch64-apple-darwin + // 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(_) => { out_args.extend(["-Wl,-dylib".to_string()]); @@ -1369,6 +1380,8 @@ impl BuildRequest { } // android/linux need to be compatible with lld + // + // android currently drags along its own libraries and other zany flags OperatingSystem::Linux if triple.environment == Environment::Android => { out_args.extend( [ @@ -1899,7 +1912,6 @@ impl BuildRequest { } fn platform_exe_name(&self) -> String { - use convert_case::{Case, Casing}; match self.platform { Platform::MacOS => self.executable_name().to_string(), Platform::Ios => self.executable_name().to_string(), @@ -2841,10 +2853,10 @@ impl BuildRequest { } // Make sure to optimize the main wasm file if requested or if bundle splitting - // if should_bundle_split || self.release { - // ctx.status_optimizing_wasm(); - // wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; - // } + if should_bundle_split || self.release { + ctx.status_optimizing_wasm(); + wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; + } // Make sure to register the main wasm file with the asset system assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; @@ -3118,10 +3130,6 @@ impl BuildRequest { self.platform_dir().join("incremental-cache") } - pub(crate) fn link_args_file(&self) -> PathBuf { - self.incremental_cache_dir().join("link_args.txt") - } - /// Check for tooling that might be required for this build. /// /// This should generally be only called on the first build since it takes time to verify the tooling diff --git a/packages/cli/src/build/tools.rs b/packages/cli/src/build/tools.rs index 64fc763b06..d7cfbb4ff9 100644 --- a/packages/cli/src/build/tools.rs +++ b/packages/cli/src/build/tools.rs @@ -1,10 +1,8 @@ -use crate::Result; -use anyhow::Context; use itertools::Itertools; use std::{path::PathBuf, sync::Arc}; use target_lexicon::{ - Aarch64Architecture, Architecture, ArmArchitecture, BinaryFormat, Environment, OperatingSystem, - Triple, Vendor, X86_32Architecture, + Aarch64Architecture, Architecture, ArmArchitecture, Environment, OperatingSystem, Triple, + X86_32Architecture, }; use tokio::process::Command; @@ -19,7 +17,7 @@ pub(crate) struct AndroidTools { } #[memoize::memoize] -pub fn android_tools() -> Option { +pub fn android_tools() -> Option> { // We check for SDK first since users might install Android Studio and then install the SDK // After that they might install the NDK, so the SDK drives the source of truth. let sdk = var_or_debug("ANDROID_SDK_ROOT") @@ -113,11 +111,11 @@ pub fn android_tools() -> Option { None }); - Some(AndroidTools { + Some(Arc::new(AndroidTools { ndk, adb, java_home, - }) + })) } impl AndroidTools { @@ -147,8 +145,16 @@ impl AndroidTools { .path() } + /// Return the location of the clang toolchain for the given target triple. + /// + /// Note that we use clang: + /// "~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" + /// + /// But if we needed the linker, we would use: + /// "~/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/ld" + /// + /// However, for our purposes, we only go through the cc driver and not the linker directly. pub(crate) fn android_cc(&self, triple: &Triple) -> PathBuf { - // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" let suffix = if cfg!(target_os = "windows") { ".cmd" } else { @@ -163,22 +169,6 @@ impl AndroidTools { )) } - pub(crate) fn android_ld(&self, triple: &Triple) -> PathBuf { - // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/ld" - let suffix = if cfg!(target_os = "windows") { - ".cmd" - } else { - "" - }; - - self.android_tools_dir().join(format!( - "{}{}-clang++{}", - triple, - self.min_sdk_version(), - suffix - )) - } - // todo(jon): this should be configurable pub(crate) fn min_sdk_version(&self) -> u32 { 24 @@ -198,13 +188,6 @@ impl AndroidTools { pub(crate) fn java_home(&self) -> Option { self.java_home.clone() - // copilot suggested this?? - // self.ndk.join("platforms").join("android-24").join("arch-arm64").join("usr").join("lib") - // .join("jvm") - // .join("default") - // .join("lib") - // .join("server") - // .join("libjvm.so") } pub(crate) fn android_jnilib(triple: &Triple) -> &'static str { @@ -218,26 +201,7 @@ impl AndroidTools { } } - // todo: the new Triple type might be able to handle the different arm flavors - // ie armv7 vs armv7a - pub(crate) fn android_clang_triplet(triple: &Triple) -> String { - use target_lexicon::Architecture; - match triple.architecture { - Architecture::Arm(_) => "armv7a-linux-androideabi".to_string(), - _ => triple.to_string(), - } - } - - // pub(crate) fn android_target_triplet(&self) -> &'static str { - // match self { - // Arch::Arm => "armv7-linux-androideabi", - // Arch::Arm64 => "aarch64-linux-android", - // Arch::X86 => "i686-linux-android", - // Arch::X64 => "x86_64-linux-android", - // } - // } - - pub async fn autodetect_android_triple(&self) -> Triple { + pub(crate) async fn autodetect_android_device_triple(&self) -> Triple { // Use the host's triple and then convert field by field // ie, the "best" emulator for an m1 mac would be: "aarch64-linux-android" // - We assume android is always "linux" diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 091b789729..e8e114fd5a 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -72,8 +72,8 @@ pub(crate) struct BuildArgs { /// /// cargo rustc -- -Clink-arg=-Wl,-blah /// - #[clap(value_delimiter = ' ')] - pub(crate) rustc_args: Vec, + #[clap(long)] + pub(crate) rustc_args: Option, /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] #[clap(long)] diff --git a/packages/cli/src/cli/config.rs b/packages/cli/src/cli/config.rs index c82c59ed0c..b864871695 100644 --- a/packages/cli/src/cli/config.rs +++ b/packages/cli/src/cli/config.rs @@ -1,6 +1,5 @@ use super::*; -use crate::TraceSrc; -use crate::{metadata::crate_root, CliSettings}; +use crate::{CliSettings, TraceSrc, Workspace}; /// Dioxus config file controls #[derive(Clone, Debug, Deserialize, Subcommand)] @@ -72,7 +71,7 @@ impl From for bool { impl Config { pub(crate) async fn config(self) -> Result { - let crate_root = crate_root()?; + let crate_root = Workspace::crate_root_from_path()?; match self { Config::Init { name, force } => { let conf_path = crate_root.join("Dioxus.toml"); diff --git a/packages/cli/src/cli/mod.rs b/packages/cli/src/cli/mod.rs index fafdacca58..cdc4d93f5d 100644 --- a/packages/cli/src/cli/mod.rs +++ b/packages/cli/src/cli/mod.rs @@ -29,7 +29,7 @@ use std::{ fs::File, io::{Read, Write}, path::PathBuf, - process::{Command, Stdio}, + process::Command, }; /// Build, Bundle & Ship Dioxus Apps. diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index d508747929..d72f78d31a 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -11,7 +11,6 @@ mod dx_build_info; mod error; mod fastfs; mod logging; -mod metadata; mod platform; mod rustcwrapper; mod serve; diff --git a/packages/cli/src/metadata.rs b/packages/cli/src/metadata.rs deleted file mode 100644 index f1f7e01870..0000000000 --- a/packages/cli/src/metadata.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Utilities for working with cargo and rust files -use std::{ - env, fs, - path::{Path, PathBuf}, -}; - -/// How many parent folders are searched for a `Cargo.toml` -const MAX_ANCESTORS: u32 = 10; - -/// Returns the root of the crate that the command is run from -/// -/// If the command is run from the workspace root, this will return the top-level Cargo.toml -pub(crate) fn crate_root() -> crate::Result { - // From the current directory we work our way up, looking for `Cargo.toml` - env::current_dir() - .ok() - .and_then(|mut wd| { - for _ in 0..MAX_ANCESTORS { - if contains_manifest(&wd) { - return Some(wd); - } - if !wd.pop() { - break; - } - } - None - }) - .ok_or_else(|| { - crate::Error::Cargo("Failed to find directory containing Cargo.toml".to_string()) - }) -} - -/// Checks if the directory contains `Cargo.toml` -fn contains_manifest(path: &Path) -> bool { - fs::read_dir(path) - .map(|entries| { - entries - .filter_map(Result::ok) - .any(|ent| &ent.file_name() == "Cargo.toml") - }) - .unwrap_or(false) -} diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 93d83da4b3..3d942115fc 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -1,5 +1,5 @@ use crate::{ - AppBuilder, BuildMode, BuildRequest, BuildUpdate, Error, Platform, Result, ServeArgs, + AppBuilder, BuildMode, BuildRequest, BuilderUpdate, Error, Platform, Result, ServeArgs, TraceController, TraceSrc, }; @@ -41,7 +41,7 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { // Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc let mut screen = Output::start(args.is_interactive_tty()).await?; - let mut builder = AppRunner::start(args).await?; + let mut builder = AppServer::start(args).await?; let mut devserver = WebServer::start(&builder)?; // This is our default splash screen. We might want to make this a fancier splash screen in the future @@ -126,14 +126,14 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { // todo: there might be more things to do here that require coordination with other pieces of the CLI // todo: maybe we want to shuffle the runner around to send an "open" command instead of doing that match update { - BuildUpdate::Progress { .. } => {} - BuildUpdate::CompilerMessage { message } => { + BuilderUpdate::Progress { .. } => {} + BuilderUpdate::CompilerMessage { message } => { screen.push_cargo_log(message); } - BuildUpdate::BuildFailed { err } => { + BuilderUpdate::BuildFailed { err } => { tracing::error!("Build failed: {:?}", err); } - BuildUpdate::BuildReady { bundle } => { + BuilderUpdate::BuildReady { bundle } => { match bundle.mode { BuildMode::Thin { .. } => { // We need to patch the app with the new bundle @@ -158,13 +158,13 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { } } } - BuildUpdate::StdoutReceived { msg } => { + BuilderUpdate::StdoutReceived { msg } => { screen.push_stdio(platform, msg, tracing::Level::INFO); } - BuildUpdate::StderrReceived { msg } => { + BuilderUpdate::StderrReceived { msg } => { screen.push_stdio(platform, msg, tracing::Level::ERROR); } - BuildUpdate::ProcessExited { status } => { + BuilderUpdate::ProcessExited { status } => { if status.success() { tracing::info!( r#"Application [{platform}] exited gracefully. diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index b865e2c1aa..5e88888ea0 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -1,6 +1,6 @@ use crate::{ serve::{ansi_buffer::AnsiStringLine, ServeUpdate, WebServer}, - BuildStage, BuildUpdate, Platform, TraceContent, TraceMsg, TraceSrc, + BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc, }; use crossterm::{ cursor::{Hide, Show}, @@ -25,7 +25,7 @@ use std::{ }; use tracing::Level; -use super::AppRunner; +use super::AppServer; const TICK_RATE_MS: u64 = 100; const VIEWPORT_MAX_WIDTH: u16 = 100; @@ -69,7 +69,7 @@ pub struct Output { #[allow(unused)] #[derive(Clone, Copy)] struct RenderState<'a> { - runner: &'a AppRunner, + runner: &'a AppServer, server: &'a WebServer, } @@ -370,19 +370,19 @@ impl Output { /// approach, but then we'd need to do that *everywhere* instead of simply performing a react-like /// re-render when external state changes. Ratatui will diff the intermediate buffer, so we at least /// we won't be drawing it. - pub(crate) fn new_build_update(&mut self, update: &BuildUpdate) { + pub(crate) fn new_build_update(&mut self, update: &BuilderUpdate) { match update { - BuildUpdate::Progress { + BuilderUpdate::Progress { stage: BuildStage::Starting { .. }, } => self.tick_animation = true, - BuildUpdate::BuildReady { .. } => self.tick_animation = false, - BuildUpdate::BuildFailed { .. } => self.tick_animation = false, + BuilderUpdate::BuildReady { .. } => self.tick_animation = false, + BuilderUpdate::BuildFailed { .. } => self.tick_animation = false, _ => {} } } /// Render the current state of everything to the console screen - pub fn render(&mut self, runner: &AppRunner, server: &WebServer) { + pub fn render(&mut self, runner: &AppServer, server: &WebServer) { if !self.interactive { return; } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index a09ae5c694..e41cfed1b4 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -45,7 +45,7 @@ use tokio::process::Command; /// /// It also holds the watcher which is used to watch for changes in the filesystem and trigger rebuilds, /// hotreloads, asset updates, etc. -pub(crate) struct AppRunner { +pub(crate) struct AppServer { /// the platform of the "primary" crate (ie the first) pub(crate) workspace: Arc, @@ -85,7 +85,7 @@ pub(crate) struct CachedFile { templates: HashMap, } -impl AppRunner { +impl AppServer { /// Create the AppRunner and then initialize the filemap with the crate directory. pub(crate) async fn start(args: ServeArgs) -> Result { let workspace = Workspace::current().await?; diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index e9f728d027..187aeb04f0 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -1,7 +1,7 @@ use crate::{ config::WebHttpsConfig, serve::{ServeArgs, ServeUpdate}, - BuildRequest, BuildStage, BuildUpdate, Platform, Result, TraceSrc, + BuildRequest, BuildStage, BuilderUpdate, Platform, Result, TraceSrc, }; use anyhow::Context; use axum::{ @@ -46,7 +46,7 @@ use tower_http::{ ServiceBuilderExt, }; -use super::{AppBuilder, AppRunner}; +use super::{AppBuilder, AppServer}; /// The webserver that serves statics assets (if fullstack isn't already doing that) and the websocket /// communication layer that we use to send status updates and hotreloads to the client. @@ -77,7 +77,7 @@ impl WebServer { /// /// This will also start the websocket server that powers the devtools. If you want to communicate /// with connected devtools clients, this is the place to do it. - pub(crate) fn start(runner: &AppRunner) -> Result { + pub(crate) fn start(runner: &AppServer) -> Result { let (hot_reload_sockets_tx, hot_reload_sockets_rx) = futures_channel::mpsc::unbounded(); let (build_status_sockets_tx, build_status_sockets_rx) = futures_channel::mpsc::unbounded(); @@ -226,9 +226,9 @@ impl WebServer { } /// Sends an updated build status to all clients. - pub(crate) async fn new_build_update(&mut self, update: &BuildUpdate) { + pub(crate) async fn new_build_update(&mut self, update: &BuilderUpdate) { match update { - BuildUpdate::Progress { stage } => { + BuilderUpdate::Progress { stage } => { // Todo(miles): wire up more messages into the splash screen UI match stage { BuildStage::Success => {} @@ -256,9 +256,9 @@ impl WebServer { _ => {} } } - BuildUpdate::CompilerMessage { .. } => {} - BuildUpdate::BuildReady { .. } => {} - BuildUpdate::BuildFailed { err } => { + BuilderUpdate::CompilerMessage { .. } => {} + BuilderUpdate::BuildReady { .. } => {} + BuilderUpdate::BuildFailed { err } => { let error = err.to_string(); self.build_status.set(Status::BuildError { error: ansi_to_html::convert(&error).unwrap_or(error), @@ -266,9 +266,9 @@ impl WebServer { self.send_reload_failed().await; self.send_build_status().await; } - BuildUpdate::StdoutReceived { msg } => {} - BuildUpdate::StderrReceived { msg } => {} - BuildUpdate::ProcessExited { status } => {} + BuilderUpdate::StdoutReceived { msg } => {} + BuilderUpdate::StderrReceived { msg } => {} + BuilderUpdate::ProcessExited { status } => {} } } @@ -426,7 +426,7 @@ async fn devserver_mainloop( /// - Setting up the file serve service /// - Setting up the websocket endpoint for devtools fn build_devserver_router( - runner: &AppRunner, + runner: &AppServer, hot_reload_sockets: UnboundedSender, build_status_sockets: UnboundedSender, fullstack_address: Option, @@ -525,7 +525,7 @@ fn build_devserver_router( Ok(router) } -fn build_serve_dir(runner: &AppRunner) -> axum::routing::MethodRouter { +fn build_serve_dir(runner: &AppServer) -> axum::routing::MethodRouter { use tower::ServiceBuilder; static CORS_UNSAFE: (HeaderValue, HeaderValue) = ( diff --git a/packages/cli/src/serve/update.rs b/packages/cli/src/serve/update.rs index 514217a797..153b829f42 100644 --- a/packages/cli/src/serve/update.rs +++ b/packages/cli/src/serve/update.rs @@ -1,6 +1,6 @@ -use crate::{BuildId, BuildUpdate, Platform, TraceMsg}; +use crate::{BuildId, BuilderUpdate, Platform, TraceMsg}; use axum::extract::ws::Message as WsMessage; -use std::{path::PathBuf, process::ExitStatus}; +use std::path::PathBuf; /// One fat enum to rule them all.... /// @@ -16,7 +16,7 @@ pub(crate) enum ServeUpdate { /// An update regarding the state of the build and running app from an AppBuilder BuilderUpdate { id: BuildId, - update: BuildUpdate, + update: BuilderUpdate, }, FilesChanged { diff --git a/packages/cli/src/wasm_bindgen.rs b/packages/cli/src/wasm_bindgen.rs index 9bc9ec71a2..774a922417 100644 --- a/packages/cli/src/wasm_bindgen.rs +++ b/packages/cli/src/wasm_bindgen.rs @@ -1,8 +1,7 @@ use crate::{CliSettings, Result}; use anyhow::{anyhow, Context}; use flate2::read::GzDecoder; -use std::path::PathBuf; -use std::{path::Path, process::Stdio}; +use std::path::{Path, PathBuf}; use tar::Archive; use tempfile::TempDir; use tokio::{fs, process::Command}; diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index b52a594558..cb283aa0b8 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -1,18 +1,15 @@ use crate::config::DioxusConfig; use crate::CliSettings; -use crate::{Platform, Result}; +use crate::Result; use anyhow::Context; use ignore::gitignore::Gitignore; -use itertools::Itertools; -use krates::{cm::Target, KrateDetails}; -use krates::{cm::TargetKind, Cmd, Krates, NodeId}; -use once_cell::sync::OnceCell; -use std::path::Path; +use krates::KrateDetails; +use krates::{Cmd, Krates, NodeId}; use std::path::PathBuf; use std::sync::Arc; +use std::{path::Path, sync::Mutex}; use target_lexicon::Triple; use tokio::process::Command; -use toml_edit::Item; pub struct Workspace { pub(crate) krates: Krates, @@ -23,11 +20,18 @@ pub struct Workspace { pub(crate) ignore: Gitignore, } -static WS: OnceCell> = OnceCell::new(); - impl Workspace { pub async fn current() -> Result> { - tracing::debug!("Loading workspace"); + static WS: Mutex>> = Mutex::new(None); + + // Lock the workspace to prevent multiple threads from loading it at the same time + // If loading the workspace failed the first time, it won't be set and therefore permeate an error. + let mut lock = WS.lock().ok().context("Workspace lock is poisoned!")?; + if let Some(ws) = lock.as_ref() { + return Ok(ws.clone()); + } + + tracing::debug!("Loading workspace!"); let cmd = Cmd::new(); let builder = krates::Builder::new(); @@ -54,14 +58,18 @@ impl Workspace { let ignore = Self::workspace_gitignore(krates.workspace_root().as_std_path()); - Ok(Arc::new(Self { + let workspace = Arc::new(Self { krates, settings, wasm_opt, sysroot: sysroot.trim().into(), rustc_version: rustc_version.trim().into(), ignore, - })) + }); + + lock.replace(workspace.clone()); + + Ok(workspace) } pub fn wasm_ld(&self) -> PathBuf { @@ -80,7 +88,7 @@ impl Workspace { .exists() } - /// Find the main package in the workspace + /// Find the "main" package in the workspace. There might not be one! pub fn find_main_package(&self, package: Option) -> Result { if let Some(package) = package { let mut workspace_members = self.krates.workspace_members(); @@ -251,8 +259,41 @@ impl Workspace { }) } - pub fn workspace_dir(&self) -> PathBuf { - self.krates.workspace_root().as_std_path().to_path_buf() + /// Returns the root of the crate that the command is run from, without calling `cargo metadata` + /// + /// If the command is run from the workspace root, this will return the top-level Cargo.toml + pub(crate) fn crate_root_from_path() -> Result { + /// How many parent folders are searched for a `Cargo.toml` + const MAX_ANCESTORS: u32 = 10; + + /// Checks if the directory contains `Cargo.toml` + fn contains_manifest(path: &Path) -> bool { + std::fs::read_dir(path) + .map(|entries| { + entries + .filter_map(Result::ok) + .any(|ent| &ent.file_name() == "Cargo.toml") + }) + .unwrap_or(false) + } + + // From the current directory we work our way up, looking for `Cargo.toml` + std::env::current_dir() + .ok() + .and_then(|mut wd| { + for _ in 0..MAX_ANCESTORS { + if contains_manifest(&wd) { + return Some(wd); + } + if !wd.pop() { + break; + } + } + None + }) + .ok_or_else(|| { + crate::Error::Cargo("Failed to find directory containing Cargo.toml".to_string()) + }) } } From 5067564346bb35fa1420e54ab3f394d24986bbd0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 17:02:23 -0700 Subject: [PATCH 139/301] more ws cleanup --- Cargo.lock | 59 ++++--------------- Cargo.toml | 2 - packages/cli/Cargo.toml | 8 ++- packages/cli/src/build/builder.rs | 2 +- packages/cli/src/build/mod.rs | 2 + .../src/lib.rs => cli/src/build/patch.rs} | 3 +- packages/cli/src/build/request.rs | 5 +- packages/cli/src/serve/runner.rs | 7 +-- packages/cli/src/serve/server.rs | 2 +- .../subsecond-cli-support/Cargo.toml | 31 ---------- packages/subsecond/subsecond/src/lib.rs | 2 +- 11 files changed, 27 insertions(+), 96 deletions(-) rename packages/{subsecond/subsecond-cli-support/src/lib.rs => cli/src/build/patch.rs} (99%) delete mode 100644 packages/subsecond/subsecond-cli-support/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 98c8a15f34..402c974c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1420,15 +1420,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bindgen" version = "0.69.5" @@ -3699,6 +3690,7 @@ dependencies = [ "log", "manganis", "manganis-core", + "memmap", "memoize", "notify", "object 0.36.7", @@ -3717,7 +3709,7 @@ dependencies = [ "serde_json", "shell-words", "strum 0.27.1", - "subsecond-cli-support", + "subsecond-types", "syn 2.0.100", "tar", "target-lexicon 0.13.2", @@ -3738,8 +3730,11 @@ dependencies = [ "unicode-segmentation", "uuid", "walkdir", + "walrus", + "wasm-encoder 0.228.0", "wasm-opt", "wasm-split-cli", + "wasmparser 0.226.0", "which 7.0.3", ] @@ -10547,12 +10542,6 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "pretty-hex" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc83ee4a840062f368f9096d80077a9841ec117e17e7f700df81958f1451254" - [[package]] name = "pretty_assertions" version = "1.4.1" @@ -13308,34 +13297,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "subsecond-cli-support" -version = "0.6.3" -dependencies = [ - "anyhow", - "bincode", - "clap", - "id-arena", - "itertools 0.14.0", - "libc", - "memmap", - "object 0.36.7", - "pretty-hex", - "pretty_assertions", - "rayon", - "rustc-demangle", - "serde", - "subsecond-types", - "target-lexicon 0.13.2", - "tokio", - "tracing", - "tracing-subscriber", - "walkdir", - "walrus", - "wasm-encoder 0.227.1", - "wasmparser 0.226.0", -] - [[package]] name = "subsecond-types" version = "0.6.3" @@ -15669,12 +15630,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.227.1" +version = "0.228.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" +checksum = "05d30290541f2d4242a162bbda76b8f2d8b1ac59eab3568ed6f2327d52c9b2c4" dependencies = [ "leb128fmt", - "wasmparser 0.227.1", + "wasmparser 0.228.0", ] [[package]] @@ -15831,9 +15792,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.227.1" +version = "0.228.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" +checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" dependencies = [ "bitflags 2.9.0", "indexmap 2.9.0", diff --git a/Cargo.toml b/Cargo.toml index 02202750fc..5eb884931a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,6 @@ members = [ # subsecond "packages/subsecond/subsecond", - "packages/subsecond/subsecond-cli-support", "packages/subsecond/subsecond-types", # Full project examples @@ -175,7 +174,6 @@ generational-box = { path = "packages/generational-box", version = "0.6.2" } lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.6.2" } # subsecond -subsecond-cli-support = { path = "packages/subsecond/subsecond-cli-support", version = "0.6.3" } subsecond-types = { path = "packages/subsecond/subsecond-types", version = "0.6.3" } subsecond = { path = "packages/subsecond/subsecond", version = "0.6.3" } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index d6c64aa036..819b714eae 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -23,8 +23,9 @@ dioxus-cli-config = { workspace = true } dioxus-cli-opt = { workspace = true } dioxus-fullstack = { workspace = true } dioxus-dx-wire-format = { workspace = true } -subsecond-cli-support = { workspace = true } wasm-split-cli = { workspace = true } +depinfo = { workspace = true } +subsecond-types = { workspace = true } clap = { workspace = true, features = ["derive", "cargo"] } convert_case = { workspace = true } @@ -117,6 +118,9 @@ throbber-widgets-tui = "0.8.0" unicode-segmentation = "1.12.0" handlebars = "6.3.1" strum = { version = "0.27.1", features = ["derive"] } +memmap = "0.7.0" +walrus = { workspace = true, features = ["parallel"]} +wasmparser = { workspace = true } tauri-utils = { workspace = true } tauri-bundler = { workspace = true } @@ -127,8 +131,8 @@ local-ip-address = "0.6.3" dircpy = "0.3.19" plist = "1.7.0" memoize = "0.5.1" +wasm-encoder = "0.228.0" -depinfo = { workspace = true } [build-dependencies] built = { version = "0.7.5", features = ["git2"] } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index d81c403a99..333dd4dd29 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -14,7 +14,7 @@ use std::{ path::{Path, PathBuf}, process::{ExitStatus, Stdio}, }; -use subsecond_cli_support::JumpTable; +use subsecond_types::JumpTable; use tokio::{ io::{AsyncBufReadExt, BufReader, Lines}, process::{Child, ChildStderr, ChildStdout, Command}, diff --git a/packages/cli/src/build/mod.rs b/packages/cli/src/build/mod.rs index 8bf10d35a1..18b5a1bab2 100644 --- a/packages/cli/src/build/mod.rs +++ b/packages/cli/src/build/mod.rs @@ -1,10 +1,12 @@ mod builder; mod context; +mod patch; mod request; mod tools; mod web; pub(crate) use builder::*; pub(crate) use context::*; +pub(crate) use patch::*; pub(crate) use request::*; pub(crate) use tools::*; diff --git a/packages/subsecond/subsecond-cli-support/src/lib.rs b/packages/cli/src/build/patch.rs similarity index 99% rename from packages/subsecond/subsecond-cli-support/src/lib.rs rename to packages/cli/src/build/patch.rs index 37365f7f85..4b4fe6b6ba 100644 --- a/packages/subsecond/subsecond-cli-support/src/lib.rs +++ b/packages/cli/src/build/patch.rs @@ -18,8 +18,7 @@ use std::{ path::Path, }; use std::{io::Write, os::raw::c_void}; -pub use subsecond_types::*; -pub use subsecond_types::*; +use subsecond_types::*; use target_lexicon::{OperatingSystem, Triple}; use tokio::process::Command; use walkdir::WalkDir; diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index b2a95deab9..f7551135e0 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1235,7 +1235,7 @@ impl BuildRequest { // Android apps can take a long time to open, and a hot patch might've been issued in the interim, // making this hotpatch a failure. if self.platform != Platform::Web { - let stub_bytes = subsecond_cli_support::resolve_undefined( + let stub_bytes = crate::build::resolve_undefined( &self.main_exe(), &object_files, &self.triple, @@ -2708,8 +2708,7 @@ impl BuildRequest { // Lift the internal functions to exports if ctx.mode == BuildMode::Fat { let unprocessed = std::fs::read(&prebindgen)?; - let all_exported_bytes = - subsecond_cli_support::prepare_wasm_base_module(&unprocessed).unwrap(); + let all_exported_bytes = crate::build::prepare_wasm_base_module(&unprocessed).unwrap(); std::fs::write(&rustc_exe, all_exported_bytes)?; } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index e41cfed1b4..90e700fb4e 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -34,7 +34,7 @@ use std::{ time::Duration, }; use std::{path::Path, time::SystemTime}; -use subsecond_cli_support::JumpTable; +use subsecond_types::JumpTable; use syn::spanned::Spanned; use target_lexicon::Triple; use tokio::process::Command; @@ -747,8 +747,7 @@ impl AppServer { tracing::debug!("Patching {} -> {}", original.display(), new.display()); - let mut jump_table = - subsecond_cli_support::create_jump_table(&original, &new, &triple).unwrap(); + let mut jump_table = crate::build::create_jump_table(&original, &new, &triple).unwrap(); // If it's android, we need to copy the assets to the device and then change the location of the patch if client.build.platform == Platform::Android { @@ -761,7 +760,7 @@ impl AppServer { if triple.architecture == target_lexicon::Architecture::Wasm32 { let old_bytes = std::fs::read(&original).unwrap(); let new_bytes = std::fs::read(&jump_table.lib).unwrap(); - let res_ = subsecond_cli_support::satisfy_got_imports(&old_bytes, &new_bytes).unwrap(); + let res_ = crate::build::satisfy_got_imports(&old_bytes, &new_bytes).unwrap(); std::fs::write(&jump_table.lib, res_).unwrap(); // make sure we use the dir relative to the public dir diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 187aeb04f0..1be93e2454 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -38,7 +38,7 @@ use std::{ sync::{Arc, RwLock}, time::Duration, }; -use subsecond_cli_support::JumpTable; +use subsecond_types::JumpTable; use tokio::process::Command; use tower_http::{ cors::Any, diff --git a/packages/subsecond/subsecond-cli-support/Cargo.toml b/packages/subsecond/subsecond-cli-support/Cargo.toml deleted file mode 100644 index 59dcb21b5c..0000000000 --- a/packages/subsecond/subsecond-cli-support/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "subsecond-cli-support" -edition = "2021" -version.workspace = true - -[dependencies] -object = { workspace = true, features = ["all"] } -pretty-hex = "0.4.1" -pretty_assertions = "1.4.1" -bincode = { workspace = true } -tokio = { workspace = true, features = ["full"] } -memmap = "0.7.0" -rustc-demangle = "0.1.24" -anyhow = { workspace = true } -itertools = "0.14.0" -serde = { version = "1.0.217", features = ["derive"] } -subsecond-types = { workspace = true } -libc = "0.2.155" -target-lexicon = "0.13.2" -tracing = { workspace = true } - - -walrus = { workspace = true, features = ["parallel"]} -wasmparser = { workspace = true } -id-arena = { workspace = true } -rayon = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } -clap = { workspace = true, features = ["derive"] } -walkdir = "2.5.0" -wasm-encoder = "0.227.1" - diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index aa1fb85773..5f25843690 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -125,7 +125,7 @@ //! //! If you're using Subsecond in your own application that doesn't have a runtime integration, you can //! build an integration using the [`apply_patch`] function. This function takes a `JumpTable` which -//! the subsecond-cli-support crate can generate. +//! the dioxus-cli crate can generate. //! //! To add support for the Dioxus Devtools protocol to your app, you can use the [dioxus-devtools](https://crates.io/crates/dioxus-devtools) //! crate which provides a `connect` method that will automatically apply patches to your application. From f4d29acd1fd1eb0eaa088a1ac544c0b83e0fb1e8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 17:10:01 -0700 Subject: [PATCH 140/301] more cleanup --- packages/desktop/src/app.rs | 2 +- packages/desktop/src/ipc.rs | 2 +- packages/desktop/src/launch.rs | 2 +- packages/devtools/src/lib.rs | 10 +++------- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index fc4f992324..d9052ae1b6 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -171,7 +171,7 @@ impl App { if let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() { let proxy = self.shared.proxy.clone(); dioxus_devtools::connect(endpoint, move |msg| { - _ = proxy.send_event(UserWindowEvent::DevtoolsMsg(msg)); + _ = proxy.send_event(UserWindowEvent::HotReloadEvent(msg)); }) } } diff --git a/packages/desktop/src/ipc.rs b/packages/desktop/src/ipc.rs index 23d2e33f6b..9dfbf104b9 100644 --- a/packages/desktop/src/ipc.rs +++ b/packages/desktop/src/ipc.rs @@ -28,7 +28,7 @@ pub enum UserWindowEvent { /// Handle a hotreload event, basically telling us to update our templates #[cfg(all(feature = "devtools", debug_assertions))] - DevtoolsMsg(dioxus_devtools::DevserverMsg), + HotReloadEvent(dioxus_devtools::DevserverMsg), // Windows-only drag-n-drop fix events. WindowsDragDrop(WindowId), diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 0107e2242e..d159dac8ff 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -55,7 +55,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config: UserWindowEvent::TrayIconEvent(evnt) => app.handle_tray_icon_event(evnt), #[cfg(all(feature = "devtools", debug_assertions))] - UserWindowEvent::DevtoolsMsg(msg) => app.handle_hot_reload_msg(msg), + UserWindowEvent::HotReloadEvent(msg) => app.handle_hot_reload_msg(msg), // Windows-only drag-n-drop fix events. We need to call the interpreter drag-n-drop code. UserWindowEvent::WindowsDragDrop(id) => { diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index fa703a5de2..cb22d74af6 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -1,15 +1,11 @@ -use dioxus_core::{ - prelude::{consume_context, try_consume_context}, - Element, ScopeId, VirtualDom, -}; +use dioxus_core::{Element, ScopeId, VirtualDom}; pub use dioxus_devtools_types::*; use dioxus_signals::{GlobalKey, Writable}; -use std::{any::TypeId, cell::Cell, ffi::CString, path::PathBuf, rc::Rc}; +use std::cell::Cell; +pub use subsecond; use subsecond::JumpTable; use warnings::Warning; -pub use subsecond; - pub struct Devtools { main_fn: Cell Element>, } From 6a232cdaa186f41420551d4d18a27b34c93a113c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 17:15:52 -0700 Subject: [PATCH 141/301] migrate build id to cli-config --- Cargo.lock | 1 + packages/cli-config/src/lib.rs | 14 ++++++++++++ packages/devtools/Cargo.toml | 1 + packages/devtools/src/lib.rs | 41 ++++------------------------------ packages/server/src/launch.rs | 3 ++- packages/web/src/devtools.rs | 2 +- 6 files changed, 23 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 402c974c5e..76feaa3232 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3931,6 +3931,7 @@ dependencies = [ name = "dioxus-devtools" version = "0.6.3" dependencies = [ + "dioxus-cli-config", "dioxus-core", "dioxus-devtools-types", "dioxus-signals", diff --git a/packages/cli-config/src/lib.rs b/packages/cli-config/src/lib.rs index 640f6d7392..2a53b6df6e 100644 --- a/packages/cli-config/src/lib.rs +++ b/packages/cli-config/src/lib.rs @@ -306,3 +306,17 @@ pub fn session_cache_dir() -> Option { pub fn android_session_cache_dir() -> PathBuf { PathBuf::from("/data/local/tmp/dx/") } + +/// The unique build id for this application, used to disambiguate between different builds of the same +/// application. +pub fn build_id() -> u64 { + #[cfg(target_arch = "wasm32")] + { + 0 + } + + #[cfg(not(target_arch = "wasm32"))] + { + 1 + } +} diff --git a/packages/devtools/Cargo.toml b/packages/devtools/Cargo.toml index 1d2a9f67d2..17ee44e8fc 100644 --- a/packages/devtools/Cargo.toml +++ b/packages/devtools/Cargo.toml @@ -13,6 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "hot-reloading"] dioxus-signals = { workspace = true } dioxus-core = { workspace = true, features = ["serialize"] } dioxus-devtools-types = { workspace = true } +dioxus-cli-config = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } subsecond = { workspace = true } diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index cb22d74af6..1767013e41 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -1,26 +1,9 @@ -use dioxus_core::{Element, ScopeId, VirtualDom}; -pub use dioxus_devtools_types::*; +use dioxus_core::{ScopeId, VirtualDom}; use dioxus_signals::{GlobalKey, Writable}; -use std::cell::Cell; -pub use subsecond; -use subsecond::JumpTable; use warnings::Warning; -pub struct Devtools { - main_fn: Cell Element>, -} - -impl Devtools { - pub fn new(entry: fn() -> Element) -> Self { - Self { - main_fn: Cell::new(entry), - } - } - - pub fn main_fn(&self) -> fn() -> Element { - self.main_fn.get() - } -} +pub use dioxus_devtools_types::*; +pub use subsecond; /// Applies template and literal changes to the VirtualDom /// @@ -56,10 +39,6 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), subseco }) } -pub fn apply_patch(table: JumpTable) -> Result<(), subsecond::PatchError> { - unsafe { subsecond::apply_patch(table) } -} - /// Connect to the devserver and handle its messages with a callback. /// /// This doesn't use any form of security or protocol, so it's not safe to expose to the internet. @@ -74,7 +53,7 @@ pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + _ = websocket.send(tungstenite::Message::Text( serde_json::to_string(&ClientMsg::Initialize { aslr_reference: subsecond::aslr_reference() as _, - build_id: build_id(), + build_id: dioxus_cli_config::build_id(), }) .unwrap() .into(), @@ -89,15 +68,3 @@ pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + } }); } - -pub fn build_id() -> u64 { - #[cfg(target_arch = "wasm32")] - { - 0 - } - - #[cfg(not(target_arch = "wasm32"))] - { - 1 - } -} diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index fc5c9a138c..af0640e07b 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -141,7 +141,8 @@ async fn serve_server( use axum::body::Body; use http::{Method, Request, Response, StatusCode}; - if let Ok(_) = dioxus_devtools::apply_patch(table) { + if let Ok(_) = unsafe { dioxus_devtools::subsecond::apply_patch(table) } + { let mut new_router = axum::Router::new().serve_static_assets(); let server_fn_iter = collect_raw_server_fns(); diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index d304dfe738..74ee9d2849 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -165,7 +165,7 @@ fn make_ws(tx: UnboundedSender, poll_interval: i32, reload: bool) } else { ws_tx.send_with_str( &serde_json::to_string(&ClientMsg::Initialize { - build_id: dioxus_devtools::build_id(), + build_id: dioxus_cli_config::build_id(), aslr_reference: dioxus_devtools::subsecond::aslr_reference() as _, }) .unwrap(), From 6b06ac4764835ab70dc52d5aace47ce8a01ea9d2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 18:25:13 -0700 Subject: [PATCH 142/301] add command file handling --- packages/cli/src/cli/link.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index 140fa6a600..ec404624df 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -54,7 +54,35 @@ impl LinkAction { /// The file will be given by the dx-magic-link-arg env var itself, so we use /// it both for determining if we should act as a linker and the for the file name itself. pub(crate) async fn run(self) -> Result<()> { - let args = std::env::args().collect::>(); + let mut args: Vec<_> = std::env::args().collect(); + + // Handle command files, usually a windows thing. + if let Some(command) = args.iter().find(|arg| arg.starts_with('@')).cloned() { + let path = command.trim().trim_start_matches('@'); + let file_binary = std::fs::read(path).unwrap(); + + // This may be a utf-16le file. Let's try utf-8 first. + let content = String::from_utf8(file_binary.clone()).unwrap_or_else(|_| { + // Convert Vec to Vec to convert into a String + let binary_u16le: Vec = file_binary + .chunks_exact(2) + .map(|a| u16::from_le_bytes([a[0], a[1]])) + .collect(); + + String::from_utf16_lossy(&binary_u16le) + }); + + // Gather linker args, and reset the args to be just the linker args + args = content + .lines() + .map(|line| { + let line_parsed = line.to_string(); + let line_parsed = line_parsed.trim_end_matches('"').to_string(); + let line_parsed = line_parsed.trim_start_matches('"').to_string(); + line_parsed + }) + .collect(); + } // Write the linker args to a file for the main process to read // todo: we might need to encode these as escaped shell words in case newlines are passed From 984727eda35687a3613f8e531f1b9bdd418fb11a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 18:25:35 -0700 Subject: [PATCH 143/301] random small cleanups --- packages/cli/src/build/patch.rs | 2 +- packages/cli/src/serve/mod.rs | 2 +- packages/cli/src/serve/output.rs | 6 +--- packages/cli/src/serve/runner.rs | 22 ++++++------- packages/core/src/global_context.rs | 5 ++- packages/desktop/src/app.rs | 13 ++++---- packages/server/src/lib.rs | 42 ++++--------------------- packages/subsecond/subsecond/src/lib.rs | 15 +++------ 8 files changed, 32 insertions(+), 75 deletions(-) diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 4b4fe6b6ba..e6c837fac3 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -39,6 +39,7 @@ pub fn create_jump_table( patch: &Path, triple: &Triple, ) -> anyhow::Result { + // WASM needs its own path since the object crate leaves quite a few of the methods unimplemented if triple.architecture == target_lexicon::Architecture::Wasm32 { return create_wasm_jump_table(original, patch); } @@ -160,7 +161,6 @@ fn collect_func_ifuncs(m: &Module) -> HashMap<&str, i32> { // Globals are usually imports and thus don't add a specific offset // ie the ifunc table is offset by a global, so we don't need to push the offset out walrus::ConstExpr::Global(_) => 0, - walrus::ConstExpr::RefNull(_) => continue, walrus::ConstExpr::RefFunc(_) => continue, }; diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 3d942115fc..c250ba7667 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -106,7 +106,7 @@ pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { // Received a message from the devtools server - currently we only use this for // logging, so we just forward it the tui ServeUpdate::WsMessage { msg, platform } => { - screen.push_ws_message(Platform::Web, &msg); + screen.push_ws_message(platform, &msg); _ = builder.handle_ws_message(&msg).await; } diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 5e88888ea0..2b052c1ac6 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -470,7 +470,6 @@ impl Output { .areas(gauge_area); let client = &state.runner.client(); - self.render_single_gauge( frame, app_progress, @@ -564,9 +563,7 @@ impl Output { state: RenderState, time_taken: Option, ) { - let client = &state.runner.client(); - - let failed = client.stage == BuildStage::Failed; + let failed = state.runner.client.stage == BuildStage::Failed; let value = if failed { 1.0 } else { value.clamp(0.0, 1.0) }; let [gauge_row, _, icon] = Layout::horizontal([ @@ -638,7 +635,6 @@ impl Output { .areas(area); let client = &state.runner.client(); - frame.render_widget( Paragraph::new(Line::from(vec![ "Platform: ".gray(), diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 90e700fb4e..ca743c2a4f 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -237,10 +237,6 @@ impl AppServer { let server_wait = OptionFuture::from(server.map(|s| s.wait())); let watcher_wait = self.watcher_rx.next(); - // // If there are no running apps, we can just return pending to avoid deadlocking - // let Some(handle) = self.running.as_mut() else { - // return futures_util::future::pending().await; - // }; tokio::select! { // Wait for the client to finish @@ -760,14 +756,18 @@ impl AppServer { if triple.architecture == target_lexicon::Architecture::Wasm32 { let old_bytes = std::fs::read(&original).unwrap(); let new_bytes = std::fs::read(&jump_table.lib).unwrap(); - let res_ = crate::build::satisfy_got_imports(&old_bytes, &new_bytes).unwrap(); - std::fs::write(&jump_table.lib, res_).unwrap(); + let res = crate::build::satisfy_got_imports(&old_bytes, &new_bytes).context( + "Couldn't satisfy GOT imports for WASM - are debug symbols being stripped?", + )?; + std::fs::write(&jump_table.lib, res).unwrap(); - // make sure we use the dir relative to the public dir - let public_dir = client.build.root_dir(); + // Make sure we use the dir relative to the public dir, so the web can load it as a proper URL + // + // ie we would've shipped `/Users/foo/Projects/dioxus/target/dx/project/debug/web/public/wasm/lib.wasm` + // but we want to ship `/wasm/lib.wasm` jump_table.lib = jump_table .lib - .strip_prefix(&public_dir) + .strip_prefix(&client.build.root_dir()) .unwrap() .to_path_buf(); } @@ -790,8 +790,8 @@ impl AppServer { .as_millis() ); - // // Save this patch - // self.client.patches.push(jump_table.clone()); + // Save this patch + self.client.patches.push(jump_table.clone()); Ok(jump_table) } diff --git a/packages/core/src/global_context.rs b/packages/core/src/global_context.rs index d3fa005fe6..edefc6013c 100644 --- a/packages/core/src/global_context.rs +++ b/packages/core/src/global_context.rs @@ -485,12 +485,11 @@ pub fn use_hook_with_cleanup( /// This might need to change to a different flag in the event hooks order changes within components. /// What we really need is a way to mark components as needing a complete rebuild if they were hit by changes. pub fn force_all_dirty() { - Runtime::with(|rt| { + _ = Runtime::with(|rt| { rt.scope_states.borrow_mut().iter().for_each(|state| { if let Some(scope) = state.as_ref() { scope.needs_update(); } }); - }) - .unwrap() + }); } diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index d9052ae1b6..29e2d3fa44 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -324,16 +324,15 @@ impl App { pub fn handle_hot_reload_msg(&mut self, msg: dioxus_devtools::DevserverMsg) { use dioxus_devtools::DevserverMsg; - use crate::android_sync_lock; - match msg { DevserverMsg::HotReload(hr_msg) => { for webview in self.webviews.values_mut() { - { - let lock = android_sync_lock::android_runtime_lock(); - dioxus_devtools::apply_changes(&webview.dom, &hr_msg); - drop(lock); - } + // This is a place where wry says it's threadsafe but it's actually not. + // If we're patching the app, we want to make sure it's not going to progress in the interim. + let lock = crate::android_sync_lock::android_runtime_lock(); + _ = dioxus_devtools::apply_changes(&webview.dom, &hr_msg); + drop(lock); + webview.poll_vdom(); } diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index c932f148f1..e6e220992e 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -75,42 +75,12 @@ pub(crate) use streaming::*; pub use launch::launch; -// #[cfg(feature = "server")] -// pub mod server; - -// #[cfg(feature = "server")] -// pub use server::ServerDocument; - -// #[cfg(feature = "axum")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -// pub mod server; - -// #[cfg(feature = "axum_core")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum_core")))] -// pub mod axum_core; - // pub mod document; -// #[cfg(feature = "server")] -// mod render; -// #[cfg(feature = "server")] -// mod streaming; - -// #[cfg(feature = "server")] -// mod serve_config; - -// #[cfg(feature = "server")] -// pub use serve_config::*; - -// #[cfg(feature = "server")] -// mod server_context; // #[cfg(feature = "axum")] // #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] // pub use crate::server::*; -// #[cfg(feature = "axum_core")] -// pub use crate::axum_core::*; - // #[cfg(feature = "server")] // #[cfg_attr(docsrs, doc(cfg(feature = "server")))] // pub use crate::render::{FullstackHTMLTemplate, SSRState}; @@ -123,9 +93,9 @@ pub use launch::launch; // #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] // pub use crate::server_context::Axum; -// #[cfg(feature = "server")] -// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] -// pub use crate::server_context::{ -// extract, server_context, with_server_context, DioxusServerContext, FromContext, -// FromServerContext, ProvideServerContext, -// }; +pub use document::ServerDocument; +pub use serve_config::*; +pub use server_context::{ + extract, server_context, with_server_context, DioxusServerContext, FromContext, + FromServerContext, ProvideServerContext, +}; diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index 5f25843690..f6d6cc6e9f 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -197,15 +197,9 @@ //! apps with Dioxus Deploy (currently under construction). use std::{ - any::TypeId, backtrace, - collections::HashMap, - ffi::CStr, mem::transmute, - ops::Deref, - os::raw::c_void, panic::{panic_any, AssertUnwindSafe, UnwindSafe}, - path::{Path, PathBuf}, sync::{Arc, Mutex}, }; @@ -569,7 +563,7 @@ pub extern "C" fn aslr_reference() -> usize { /// I haven't tested it on device yet, so if if it doesn't work, then we can simply revert to using /// "adb root" and then pushing the library to the /data/data folder instead of the tmp folder. #[cfg(target_os = "android")] -unsafe fn android_memmap_dlopen(file: &Path) -> libloading::Library { +unsafe fn android_memmap_dlopen(file: &std::path::Path) -> libloading::Library { use std::ffi::{c_void, CStr, CString}; use std::os::fd::{AsRawFd, BorrowedFd}; use std::ptr; @@ -677,15 +671,14 @@ pub async unsafe fn __subsecond_wasm_patch(table: wasm_bindgen::JsValue) { } } - let table: JumpTable = JumpTable { + apply_patch(JumpTable { map, ifunc_count, lib: lib_url.into(), aslr_reference: 0, new_base_address: 0, - }; - - apply_patch(table); + }) + .expect("Failed to apply patch"); } /// A trait that enables types to be hot-patched. From b2d14a5579d82d985c309559b92101fbd09b7d61 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 16 Apr 2025 18:43:30 -0700 Subject: [PATCH 144/301] wip.... --- Cargo.toml | 14 +++++++------- examples/fullstack-hello-world/src/main.rs | 14 ++++++++++++++ packages/cli/src/build/patch.rs | 9 +++++++-- packages/cli/src/build/request.rs | 12 +++++++----- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5eb884931a..d1772239cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -332,8 +332,8 @@ winit = { version = "0.30.2", features = ["rwh_06"] } # disable debug symbols in dev builds - shouldn't matter for downstream crates but makes our binaries (examples, cli, etc) build faster [profile.dev] -debug = 1 -strip = "debuginfo" +# debug = 1 +# strip = "debuginfo" # our release profile should be fast to compile and fast to run # when we ship our CI builds, we turn on LTO which improves perf leftover by turning on incremental @@ -341,7 +341,7 @@ strip = "debuginfo" incremental = true # crank up the opt level for wasm-split-cli in dev mode -# important here that lto is on and the debug symbols are presenta (since they're used by wasm-opt)a +# important here that lto is on and the debug symbols are present (since they're used by wasm-opt)a [profile.wasm-split-release] inherits = "release" opt-level = 'z' @@ -350,8 +350,8 @@ debug = true [profile.subsecond-dev] inherits = "dev" -debug = 0 -strip = "debuginfo" +# debug = 0 +# strip = "debuginfo" # a profile for running the CLI that's also incremental [profile.cli-release-dev] @@ -376,8 +376,8 @@ incremental = true [profile.wasm-dev] inherits = "dev" -debug = 0 -strip = "debuginfo" +# debug = 0 +# strip = "debuginfo" [profile.server-dev] inherits = "dev" diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 49781915ae..f26f9bfabb 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -22,9 +22,23 @@ fn app() -> Element { "Say hi!" } "Server said: {text}" + Child {} + // Child2 { i: 123 } } } +fn Child() -> Element { + rsx! { + div { "Hello from the child component!" } + } +} +// #[component] +// fn Child2(i: i32) -> Element { +// rsx! { +// div { "Hello from the child component!" } +// } +// } + #[server] async fn say_hi() -> Result { Ok("DUAL asdasd ACHIEVED?asdasdads????!".to_string()) diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index e6c837fac3..8274725398 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -12,7 +12,7 @@ use object::{ RelocationFlags, RelocationTarget, SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, }; -use std::{cmp::Ordering, ffi::OsStr, fs, ops::Deref, path::PathBuf}; +use std::{cmp::Ordering, ffi::OsStr, fs, ops::Deref, panic, path::PathBuf}; use std::{ collections::{BTreeMap, HashMap, HashSet}, path::Path, @@ -628,7 +628,12 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result // Collect the GOT func/mem entries for t in new.imports.iter() { match t.module.as_str() { - "GOT.func" => funcs.push((t.id(), *ifunc_map.get(t.name.as_str()).unwrap())), + "GOT.func" => funcs.push(( + t.id(), + *ifunc_map + .get(t.name.as_str()) + .unwrap_or_else(|| panic!("failed to find GOT.func: {}", t.name.as_str())), + )), "GOT.mem" => mems.push(t.id()), _ => {} } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index f7551135e0..d2682b42fb 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1351,6 +1351,8 @@ impl BuildRequest { "--import-table".to_string(), "--growable-table".to_string(), "--export-all".to_string(), + "--export".to_string(), + "main".to_string(), "--allow-undefined".to_string(), "--no-demangle".to_string(), "--no-entry".to_string(), @@ -2851,11 +2853,11 @@ impl BuildRequest { std::fs::write(&post_bindgen_wasm, modules.main.bytes)?; } - // Make sure to optimize the main wasm file if requested or if bundle splitting - if should_bundle_split || self.release { - ctx.status_optimizing_wasm(); - wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; - } + // // Make sure to optimize the main wasm file if requested or if bundle splitting + // if should_bundle_split || self.release { + // ctx.status_optimizing_wasm(); + // wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; + // } // Make sure to register the main wasm file with the asset system assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; From 8bebab905b01000f52cbc9a5c3ec96c527fbf41a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 12:46:58 -0700 Subject: [PATCH 145/301] wip.... --- Cargo.lock | 792 +-------------------- examples/fullstack-hello-world/src/main.rs | 19 +- packages/cli-opt/Cargo.toml | 54 +- packages/cli-opt/src/js.rs | 459 ++++++------ packages/cli/src/build/patch.rs | 296 ++++---- packages/cli/src/build/request.rs | 4 +- packages/core-macro/src/props/mod.rs | 8 +- packages/core/README.md | 2 +- packages/core/src/virtual_dom.rs | 22 + packages/dioxus-lib/README.md | 2 +- packages/signals/docs/memo.md | 4 +- packages/signals/docs/signals.md | 2 +- packages/signals/src/impls.rs | 4 +- packages/ssr/README.md | 4 +- 14 files changed, 478 insertions(+), 1194 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76feaa3232..e95b93f1c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -459,7 +459,7 @@ dependencies = [ "scroll", "security-framework 2.11.1", "security-framework-sys", - "semver 1.0.26", + "semver", "serde", "serde_json", "serde_yaml", @@ -683,18 +683,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ast_node" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.100", -] - [[package]] name = "async-broadcast" version = "0.7.2" @@ -1053,17 +1041,6 @@ dependencies = [ "terminal-prompt", ] -[[package]] -name = "auto_impl" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -1398,15 +1375,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "better_scoped_tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664" -dependencies = [ - "scoped-tls", -] - [[package]] name = "bigdecimal" version = "0.4.8" @@ -1829,9 +1797,6 @@ name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" -dependencies = [ - "allocator-api2", -] [[package]] name = "bytecheck" @@ -1840,7 +1805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", - "ptr_meta 0.1.4", + "ptr_meta", "simdutf8", ] @@ -2035,7 +2000,7 @@ dependencies = [ "remove_dir_all", "rhai", "sanitize-filename", - "semver 1.0.26", + "semver", "serde", "tempfile", "thiserror 2.0.12", @@ -2059,7 +2024,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f905f68f8cb8a8182592d9858a5895360f0a5b08b6901fdb10498fb91829804" dependencies = [ - "semver 1.0.26", + "semver", "serde", "serde-untagged", "serde-value", @@ -2077,7 +2042,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver", "serde", "serde_json", "thiserror 2.0.12", @@ -2834,30 +2799,15 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" -dependencies = [ - "crc-catalog 1.1.1", -] - [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ - "crc-catalog 2.4.0", + "crc-catalog", ] -[[package]] -name = "crc-catalog" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" - [[package]] name = "crc-catalog" version = "2.4.0" @@ -3173,7 +3123,7 @@ dependencies = [ "digest", "fiat-crypto 0.2.9", "rand_core 0.6.4", - "rustc_version 0.4.1", + "rustc_version", "subtle", "zeroize", ] @@ -3369,16 +3319,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "serde", - "uuid", -] - [[package]] name = "depinfo" version = "0.1.0" @@ -3458,7 +3398,7 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.1", + "rustc_version", "syn 2.0.100", ] @@ -3765,32 +3705,6 @@ dependencies = [ "rayon", "serde", "serde_json", - "swc_allocator", - "swc_atoms", - "swc_bundler", - "swc_cached", - "swc_common", - "swc_config", - "swc_config_macro", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_codegen_macros", - "swc_ecma_loader", - "swc_ecma_minifier", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_transforms_optimization", - "swc_ecma_usage_analyzer", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_eq_ignore_macros", - "swc_fast_graph", - "swc_graph_analyzer", - "swc_macros_common", - "swc_parallel", - "swc_timer", - "swc_visit", "tracing", ] @@ -5190,7 +5104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", - "rustc_version 0.4.1", + "rustc_version", ] [[package]] @@ -5397,17 +5311,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "from_variant" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" -dependencies = [ - "proc-macro2", - "swc_macros_common", - "syn 2.0.100", -] - [[package]] name = "fs-err" version = "3.0.0" @@ -6615,15 +6518,6 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.11", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -6777,20 +6671,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0" -dependencies = [ - "hashbrown 0.14.5", - "new_debug_unreachable", - "once_cell", - "phf 0.11.3", - "rustc-hash 1.1.0", - "triomphe", -] - [[package]] name = "html-escape" version = "0.2.13" @@ -7263,12 +7143,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - [[package]] name = "ignore" version = "0.4.23" @@ -7542,18 +7416,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-macro" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "is-terminal" version = "0.4.16" @@ -7909,7 +7771,7 @@ dependencies = [ "camino", "cfg-expr 0.17.2", "petgraph", - "semver 1.0.26", + "semver", "serde", "serde_json", ] @@ -8338,15 +8200,6 @@ dependencies = [ "hashbrown 0.12.3", ] -[[package]] -name = "lru" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" -dependencies = [ - "hashbrown 0.13.2", -] - [[package]] name = "lru" version = "0.12.5" @@ -8994,15 +8847,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" -[[package]] -name = "normpath" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a9da8c9922c35a1033d76f7272dfc2e7ee20392083d75aeea6ced23c6266578" -dependencies = [ - "winapi", -] - [[package]] name = "normpath" version = "1.3.0" @@ -9081,7 +8925,6 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "serde", ] [[package]] @@ -9976,12 +9819,6 @@ dependencies = [ "path-dedot", ] -[[package]] -name = "path-clean" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" - [[package]] name = "path-dedot" version = "3.1.1" @@ -10709,31 +10546,13 @@ dependencies = [ "prost", ] -[[package]] -name = "psm" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" -dependencies = [ - "cc", -] - [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ - "ptr_meta_derive 0.1.4", -] - -[[package]] -name = "ptr_meta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" -dependencies = [ - "ptr_meta_derive 0.3.0", + "ptr_meta_derive", ] [[package]] @@ -10747,17 +10566,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ptr_meta_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "qoi" version = "0.4.1" @@ -10876,12 +10684,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_fmt" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" - [[package]] name = "rand" version = "0.7.3" @@ -11243,12 +11045,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" - [[package]] name = "remove_dir_all" version = "0.8.4" @@ -11259,7 +11055,7 @@ dependencies = [ "cvt", "fs_at", "libc", - "normpath 1.3.0", + "normpath", "windows-sys 0.59.0", ] @@ -11477,7 +11273,7 @@ dependencies = [ "bytecheck", "bytes", "hashbrown 0.12.3", - "ptr_meta 0.1.4", + "ptr_meta", "rend", "rkyv_derive", "seahash", @@ -11586,22 +11382,13 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver", ] [[package]] @@ -11828,12 +11615,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "ryu-js" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" - [[package]] name = "same-file" version = "1.0.6" @@ -12020,15 +11801,6 @@ dependencies = [ "to_shmem_derive", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.26" @@ -12038,12 +11810,6 @@ dependencies = [ "serde", ] -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -12283,7 +12049,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "rustc_version 0.4.1", + "rustc_version", "syn 2.0.100", "xxhash-rust", ] @@ -12673,25 +12439,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "sourcemap" -version = "9.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c4ea7042fd1a155ad95335b5d505ab00d5124ea0332a06c8390d200bb1a76a" -dependencies = [ - "base64-simd", - "bitvec", - "data-encoding", - "debugid", - "if_chain", - "rustc-hash 1.1.0", - "rustc_version 0.2.3", - "serde", - "serde_json", - "unicode-id-start", - "url", -] - [[package]] name = "spake2" version = "0.4.0" @@ -12757,7 +12504,7 @@ dependencies = [ "bstr", "bytes", "chrono", - "crc 3.2.1", + "crc", "crossbeam-queue", "either", "event-listener 5.4.0", @@ -12844,7 +12591,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc 3.2.1", + "crc", "digest", "dotenvy", "either", @@ -12891,7 +12638,7 @@ dependencies = [ "bitflags 2.9.0", "byteorder", "chrono", - "crc 3.2.1", + "crc", "dotenvy", "etcetera", "futures-channel", @@ -12958,19 +12705,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "static-self" version = "0.1.2" @@ -13033,18 +12767,6 @@ dependencies = [ "quote", ] -[[package]] -name = "string_enum" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.100", -] - [[package]] name = "stringprep" version = "0.1.5" @@ -13366,446 +13088,6 @@ dependencies = [ "zeno", ] -[[package]] -name = "swc_allocator" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3" -dependencies = [ - "bumpalo", - "hashbrown 0.14.5", - "ptr_meta 0.3.0", - "rustc-hash 1.1.0", - "triomphe", -] - -[[package]] -name = "swc_atoms" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a640bf2e4430a149c87b5eaf377477ce8615ca7cb808054dd20e79e42da5d6ba" -dependencies = [ - "hstr", - "once_cell", - "rustc-hash 1.1.0", - "serde", -] - -[[package]] -name = "swc_bundler" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88a91910cd8430f88f8987019cf3a96d92a5d5dded3e0ba8203e0379e4a2f6f" -dependencies = [ - "anyhow", - "crc 2.1.0", - "indexmap 2.9.0", - "is-macro", - "once_cell", - "parking_lot", - "petgraph", - "radix_fmt", - "relative-path", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_loader", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_optimization", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_fast_graph", - "swc_graph_analyzer", - "tracing", -] - -[[package]] -name = "swc_cached" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b6a5ef4cfec51d3fa30b73600f206453a37fc30cf1141e4644a57b1ed88616" -dependencies = [ - "ahash 0.8.11", - "anyhow", - "dashmap 5.5.3", - "once_cell", - "regex", - "serde", -] - -[[package]] -name = "swc_common" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22" -dependencies = [ - "ast_node", - "better_scoped_tls", - "cfg-if", - "either", - "from_variant", - "new_debug_unreachable", - "num-bigint", - "once_cell", - "rustc-hash 1.1.0", - "serde", - "siphasher 0.3.11", - "swc_allocator", - "swc_atoms", - "swc_eq_ignore_macros", - "swc_visit", - "termcolor", - "tracing", - "unicode-width 0.1.14", - "url", -] - -[[package]] -name = "swc_config" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa30931f9b26af8edcb4cce605909d15dcfd7577220b22c50a2988f2a53c4c1" -dependencies = [ - "anyhow", - "indexmap 2.9.0", - "serde", - "serde_json", - "sourcemap", - "swc_cached", - "swc_config_macro", -] - -[[package]] -name = "swc_config_macro" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2ebd37ef52a8555c8c9be78b694d64adcb5e3bc16c928f030d82f1d65fac57" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.100", -] - -[[package]] -name = "swc_ecma_ast" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f448db2d1c52ffd2bd3788d89cafd8b5a75b97f0dc8aae00874dda2647f6b6" -dependencies = [ - "bitflags 2.9.0", - "is-macro", - "num-bigint", - "phf 0.11.3", - "scoped-tls", - "serde", - "string_enum", - "swc_atoms", - "swc_common", - "swc_visit", - "unicode-id-start", -] - -[[package]] -name = "swc_ecma_codegen" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f93692de35a77d920ce8d96a46217735e5f86bf42f76cc8f1a60628c347c4c8" -dependencies = [ - "memchr", - "num-bigint", - "once_cell", - "regex", - "serde", - "sourcemap", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_codegen_macros", - "tracing", -] - -[[package]] -name = "swc_ecma_codegen_macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9a42f479a6475647e248fa9750982c87cd985e19d1016a1fc18a70682305d1" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.100", -] - -[[package]] -name = "swc_ecma_loader" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a19b132079bfcd19d6fdabce7e55ece93a30787f3b8684c8646ddaf2237812d" -dependencies = [ - "anyhow", - "dashmap 5.5.3", - "lru 0.10.1", - "normpath 0.2.0", - "once_cell", - "parking_lot", - "path-clean", - "pathdiff", - "serde", - "serde_json", - "swc_atoms", - "swc_common", - "tracing", -] - -[[package]] -name = "swc_ecma_minifier" -version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164291b068cca947462d87ede1baf276f69da137db1a0c66059a8aed81b785b2" -dependencies = [ - "arrayvec", - "indexmap 2.9.0", - "num-bigint", - "num_cpus", - "once_cell", - "parking_lot", - "phf 0.11.3", - "radix_fmt", - "regex", - "rustc-hash 1.1.0", - "ryu-js", - "serde", - "serde_json", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_optimization", - "swc_ecma_usage_analyzer", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_parallel", - "swc_timer", - "tracing", -] - -[[package]] -name = "swc_ecma_parser" -version = "6.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92d3a25349d7f612c38d940f09f9c19c7b7aa3bf4d22fbe31ea44fd5354de02" -dependencies = [ - "either", - "new_debug_unreachable", - "num-bigint", - "num-traits", - "phf 0.11.3", - "serde", - "smallvec", - "smartstring", - "stacker", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "tracing", - "typed-arena", -] - -[[package]] -name = "swc_ecma_transforms_base" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fdc36d220bcd51f70b1d78bdd8c1e1a172b4e594c385bdd9614b84a7c0e112" -dependencies = [ - "better_scoped_tls", - "bitflags 2.9.0", - "indexmap 2.9.0", - "once_cell", - "phf 0.11.3", - "rustc-hash 1.1.0", - "serde", - "smallvec", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_parallel", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6845dfb88569f3e8cd05901505916a8ebe98be3922f94769ca49f84e8ccec8f7" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.100", -] - -[[package]] -name = "swc_ecma_transforms_optimization" -version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4232534b28fc57b745e8c709723544e5548af29abaa62281eab427099f611d" -dependencies = [ - "dashmap 5.5.3", - "indexmap 2.9.0", - "once_cell", - "petgraph", - "rustc-hash 1.1.0", - "serde_json", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_macros", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_fast_graph", - "tracing", -] - -[[package]] -name = "swc_ecma_usage_analyzer" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15eb86aaa82d7ec4c1a6c3a8a824b1fdbbaace73c3ed81035a1fbbac49f8e0bd" -dependencies = [ - "indexmap 2.9.0", - "rustc-hash 1.1.0", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_timer", - "tracing", -] - -[[package]] -name = "swc_ecma_utils" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9d22b4883dc6d6c21a8216bbf5aacedd7f104230b1557367ae126a2ec3a2b5" -dependencies = [ - "indexmap 2.9.0", - "num_cpus", - "once_cell", - "rustc-hash 1.1.0", - "ryu-js", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_visit", - "swc_parallel", - "tracing", - "unicode-id", -] - -[[package]] -name = "swc_ecma_visit" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04c06c1805bda18c27165560f1617a57453feb9fb0638d90839053641af42d4" -dependencies = [ - "new_debug_unreachable", - "num-bigint", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_visit", - "tracing", -] - -[[package]] -name = "swc_eq_ignore_macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "swc_fast_graph" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22e0a0478b1b06610453a97c8371cafa742e371a79aff860ccfbabe1ab160a7" -dependencies = [ - "indexmap 2.9.0", - "petgraph", - "rustc-hash 1.1.0", - "swc_common", -] - -[[package]] -name = "swc_graph_analyzer" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b9841af596d2ddb37e56defca81387b60a14863e251cede839d1e349e6209d" -dependencies = [ - "auto_impl", - "petgraph", - "swc_common", - "swc_fast_graph", - "tracing", -] - -[[package]] -name = "swc_macros_common" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "swc_parallel" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cde1a0f344924be62d01de0c8a98e840feae271b77dc8c1d9d2e340687225c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "swc_timer" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db06b46cc832f7cf83c2ce21905fc465d01443a2bdccf63644383e1f5847532" -dependencies = [ - "tracing", -] - -[[package]] -name = "swc_visit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3" -dependencies = [ - "either", - "new_debug_unreachable", -] - [[package]] name = "syn" version = "1.0.109" @@ -14072,7 +13354,7 @@ dependencies = [ "plist", "regex", "rpm", - "semver 1.0.26", + "semver", "serde", "serde_json", "sha1", @@ -14146,7 +13428,7 @@ dependencies = [ "memchr", "phf 0.11.3", "regex", - "semver 1.0.26", + "semver", "serde", "serde-untagged", "serde_json", @@ -14829,16 +14111,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "triomphe" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" -dependencies = [ - "serde", - "stable_deref_trait", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -14935,12 +14207,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "typed-arena" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" - [[package]] name = "typeid" version = "1.0.3" @@ -15083,18 +14349,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" -[[package]] -name = "unicode-id" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" - -[[package]] -name = "unicode-id-start" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" - [[package]] name = "unicode-ident" version = "1.0.18" @@ -15765,7 +15019,7 @@ dependencies = [ "bitflags 2.9.0", "hashbrown 0.14.5", "indexmap 2.9.0", - "semver 1.0.26", + "semver", "serde", ] @@ -15787,7 +15041,7 @@ dependencies = [ "bitflags 2.9.0", "hashbrown 0.15.2", "indexmap 2.9.0", - "semver 1.0.26", + "semver", "serde", ] @@ -15799,7 +15053,7 @@ checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" dependencies = [ "bitflags 2.9.0", "indexmap 2.9.0", - "semver 1.0.26", + "semver", ] [[package]] diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index f26f9bfabb..61921fa525 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -21,23 +21,24 @@ fn app() -> Element { }, "Say hi!" } + button { + onclick: move |_| async move { + text.set(say_hi().await.unwrap()); + text.set(say_hi().await.unwrap()); + }, + "Say hi!" + } "Server said: {text}" - Child {} - // Child2 { i: 123 } + Child2 { i: 123 } } } -fn Child() -> Element { +#[component] +fn Child2(i: i32) -> Element { rsx! { div { "Hello from the child component!" } } } -// #[component] -// fn Child2(i: i32) -> Element { -// rsx! { -// div { "Hello from the child component!" } -// } -// } #[server] async fn say_hi() -> Result { diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index 06c3210194..062696a610 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -39,31 +39,31 @@ lightningcss = { version = "1.0.0-alpha.63", features = ["browserslist", "into_o grass = "0.13.4" codemap = "0.1.3" -# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates -swc_allocator = { version = "=2.0.0", default-features = false } -swc_atoms = { version = "=3.0.2", default-features = false } -swc_bundler = { version = "=7.0.0", default-features = false } -swc_cached = { version = "=1.0.0", default-features = false } -swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false } -swc_config = { version = "=1.0.0", default-features = false } -swc_config_macro = { version = "=1.0.0", default-features = false } -swc_ecma_ast = { version = "=5.0.1", default-features = false } -swc_ecma_codegen = { version = "=5.0.1", default-features = false } -swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false } -swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false } -swc_ecma_minifier = { version = "=7.0.1", default-features = false } -swc_ecma_parser = { version = "=6.0.2", default-features = false } -swc_ecma_transforms_base = { version = "=7.0.0", default-features = false } -swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false } -swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false } -swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false } -swc_ecma_utils = { version = "=7.0.0", default-features = false } -swc_ecma_visit = { version = "=5.0.0", default-features = false } -swc_eq_ignore_macros = { version = "=1.0.0", default-features = false } -swc_fast_graph = { version = "=6.0.0", default-features = false } -swc_graph_analyzer = { version = "=5.0.0", default-features = false } -swc_macros_common = { version = "=1.0.0", default-features = false } -swc_parallel = { version = "=1.0.1", default-features = false } -swc_timer = { version = "=1.0.0", default-features = false } -swc_visit = { version = "=2.0.0", default-features = false } +# # Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates +# swc_allocator = { version = "=2.0.0", default-features = false } +# swc_atoms = { version = "=3.0.2", default-features = false } +# swc_bundler = { version = "=7.0.0", default-features = false } +# swc_cached = { version = "=1.0.0", default-features = false } +# swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false } +# swc_config = { version = "=1.0.0", default-features = false } +# swc_config_macro = { version = "=1.0.0", default-features = false } +# swc_ecma_ast = { version = "=5.0.1", default-features = false } +# swc_ecma_codegen = { version = "=5.0.1", default-features = false } +# swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false } +# swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false } +# swc_ecma_minifier = { version = "=7.0.1", default-features = false } +# swc_ecma_parser = { version = "=6.0.2", default-features = false } +# swc_ecma_transforms_base = { version = "=7.0.0", default-features = false } +# swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false } +# swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false } +# swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false } +# swc_ecma_utils = { version = "=7.0.0", default-features = false } +# swc_ecma_visit = { version = "=5.0.0", default-features = false } +# swc_eq_ignore_macros = { version = "=1.0.0", default-features = false } +# swc_fast_graph = { version = "=6.0.0", default-features = false } +# swc_graph_analyzer = { version = "=5.0.0", default-features = false } +# swc_macros_common = { version = "=1.0.0", default-features = false } +# swc_parallel = { version = "=1.0.1", default-features = false } +# swc_timer = { version = "=1.0.0", default-features = false } +# swc_visit = { version = "=2.0.0", default-features = false } browserslist-rs = { version = "=0.16.0" } diff --git a/packages/cli-opt/src/js.rs b/packages/cli-opt/src/js.rs index 1e79067a45..1d12e2f190 100644 --- a/packages/cli-opt/src/js.rs +++ b/packages/cli-opt/src/js.rs @@ -1,226 +1,231 @@ +// use std::path::Path; +// use std::path::PathBuf; + +// use anyhow::Context; +// use manganis_core::JsAssetOptions; +// use swc_common::errors::Emitter; +// use swc_common::errors::Handler; +// use swc_common::input::SourceFileInput; +// use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions}; +// use swc_ecma_parser::lexer::Lexer; +// use swc_ecma_parser::Parser; +// use swc_ecma_transforms_base::fixer::fixer; +// use swc_ecma_visit::VisitMutWith; + +// use std::collections::HashMap; + +// use anyhow::Error; +// use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord}; +// use swc_common::{ +// errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS, +// }; +// use swc_ecma_ast::*; +// use swc_ecma_codegen::text_writer::JsWriter; +// use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv}; +// use swc_ecma_parser::{parse_file_as_module, Syntax}; + +// struct TracingEmitter; + +// impl Emitter for TracingEmitter { +// fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { +// match db.level { +// swc_common::errors::Level::Bug +// | swc_common::errors::Level::Fatal +// | swc_common::errors::Level::PhaseFatal +// | swc_common::errors::Level::Error => tracing::error!("{}", db.message()), +// swc_common::errors::Level::Warning +// | swc_common::errors::Level::FailureNote +// | swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()), +// swc_common::errors::Level::Note | swc_common::errors::Level::Help => { +// tracing::trace!("{}", db.message()) +// } +// } +// } +// } + +// fn bundle_js_to_writer( +// file: PathBuf, +// bundle: bool, +// minify: bool, +// write_to: &mut impl std::io::Write, +// ) -> anyhow::Result<()> { +// let globals = Globals::new(); +// let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default()); +// GLOBALS.set(&globals, || { +// HANDLER.set(&handler, || { +// bundle_js_to_writer_inside_handler(&globals, file, bundle, minify, write_to) +// }) +// }) +// } + +// fn bundle_js_to_writer_inside_handler( +// globals: &Globals, +// file: PathBuf, +// bundle: bool, +// minify: bool, +// write_to: &mut impl std::io::Write, +// ) -> anyhow::Result<()> { +// let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); +// let mut module = if bundle { +// let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true); +// let mut bundler = Bundler::new( +// globals, +// cm.clone(), +// PathLoader { cm: cm.clone() }, +// node_resolver, +// Config { +// require: true, +// ..Default::default() +// }, +// Box::new(Hook), +// ); +// let mut entries = HashMap::default(); +// entries.insert("main".to_string(), FileName::Real(file)); + +// let mut bundles = bundler +// .bundle(entries) +// .context("failed to bundle javascript with swc")?; +// // Since we only inserted one entry, there should only be one bundle in the output +// let bundle = bundles +// .pop() +// .ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?; +// bundle.module +// } else { +// let fm = cm.load_file(Path::new(&file)).expect("Failed to load file"); + +// let lexer = Lexer::new( +// Default::default(), +// Default::default(), +// SourceFileInput::from(&*fm), +// None, +// ); +// let mut parser = Parser::new_from(lexer); + +// parser.parse_module().map_err(|err| { +// HANDLER.with(|handler| { +// let mut error = err.into_diagnostic(handler); +// // swc errors panic on drop if you don't cancel them +// error.cancel(); +// anyhow::anyhow!("{}", error.message()) +// }) +// })? +// }; + +// if minify { +// module = swc_ecma_minifier::optimize( +// std::mem::take(&mut module).into(), +// cm.clone(), +// None, +// None, +// &MinifyOptions { +// rename: true, +// compress: None, +// mangle: None, +// ..Default::default() +// }, +// &ExtraOptions { +// unresolved_mark: Mark::new(), +// top_level_mark: Mark::new(), +// mangle_name_cache: None, +// }, +// ) +// .expect_module(); +// module.visit_mut_with(&mut fixer(None)); +// } + +// let mut emitter = swc_ecma_codegen::Emitter { +// cfg: swc_ecma_codegen::Config::default().with_minify(minify), +// cm: cm.clone(), +// comments: None, +// wr: Box::new(JsWriter::new(cm, "\n", write_to, None)), +// }; + +// emitter.emit_module(&module)?; + +// Ok(()) +// } + +// struct PathLoader { +// cm: Lrc, +// } + +// impl Load for PathLoader { +// fn load(&self, file: &FileName) -> anyhow::Result { +// let file = match file { +// FileName::Real(v) => v, +// _ => anyhow::bail!("Only real files are supported"), +// }; + +// let fm = self.cm.load_file(file)?; + +// let module = HANDLER.with(|handler| { +// parse_file_as_module( +// &fm, +// Syntax::Es(Default::default()), +// Default::default(), +// None, +// &mut Vec::new(), +// ) +// .map_err(|err| { +// let mut error = err.into_diagnostic(handler); +// // swc errors panic on drop if you don't cancel them +// error.cancel(); +// anyhow::anyhow!("{}", error.message()) +// }) +// .context("Failed to parse javascript") +// })?; + +// Ok(ModuleData { +// fm, +// module, +// helpers: Default::default(), +// }) +// } +// } + +// // Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302 +// struct Hook; + +// impl swc_bundler::Hook for Hook { +// fn get_import_meta_props( +// &self, +// span: Span, +// module_record: &ModuleRecord, +// ) -> Result, Error> { +// let file_name = module_record.file_name.to_string(); + +// Ok(vec![ +// KeyValueProp { +// key: PropName::Ident(IdentName::new("url".into(), span)), +// value: Box::new(Expr::Lit(Lit::Str(Str { +// span, +// raw: None, +// value: file_name.into(), +// }))), +// }, +// KeyValueProp { +// key: PropName::Ident(IdentName::new("main".into(), span)), +// value: Box::new(if module_record.is_entry { +// Expr::Member(MemberExpr { +// span, +// obj: Box::new(Expr::MetaProp(MetaPropExpr { +// span, +// kind: MetaPropKind::ImportMeta, +// })), +// prop: MemberProp::Ident(IdentName::new("main".into(), span)), +// }) +// } else { +// Expr::Lit(Lit::Bool(Bool { span, value: false })) +// }), +// }, +// ]) +// } +// } + use std::path::Path; -use std::path::PathBuf; use anyhow::Context; -use manganis_core::JsAssetOptions; -use swc_common::errors::Emitter; -use swc_common::errors::Handler; -use swc_common::input::SourceFileInput; -use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions}; -use swc_ecma_parser::lexer::Lexer; -use swc_ecma_parser::Parser; -use swc_ecma_transforms_base::fixer::fixer; -use swc_ecma_visit::VisitMutWith; - -use std::collections::HashMap; - -use anyhow::Error; -use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord}; -use swc_common::{ - errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS, -}; -use swc_ecma_ast::*; -use swc_ecma_codegen::text_writer::JsWriter; -use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv}; -use swc_ecma_parser::{parse_file_as_module, Syntax}; - -struct TracingEmitter; - -impl Emitter for TracingEmitter { - fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { - match db.level { - swc_common::errors::Level::Bug - | swc_common::errors::Level::Fatal - | swc_common::errors::Level::PhaseFatal - | swc_common::errors::Level::Error => tracing::error!("{}", db.message()), - swc_common::errors::Level::Warning - | swc_common::errors::Level::FailureNote - | swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()), - swc_common::errors::Level::Note | swc_common::errors::Level::Help => { - tracing::trace!("{}", db.message()) - } - } - } -} - -fn bundle_js_to_writer( - file: PathBuf, - bundle: bool, - minify: bool, - write_to: &mut impl std::io::Write, -) -> anyhow::Result<()> { - let globals = Globals::new(); - let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default()); - GLOBALS.set(&globals, || { - HANDLER.set(&handler, || { - bundle_js_to_writer_inside_handler(&globals, file, bundle, minify, write_to) - }) - }) -} - -fn bundle_js_to_writer_inside_handler( - globals: &Globals, - file: PathBuf, - bundle: bool, - minify: bool, - write_to: &mut impl std::io::Write, -) -> anyhow::Result<()> { - let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let mut module = if bundle { - let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true); - let mut bundler = Bundler::new( - globals, - cm.clone(), - PathLoader { cm: cm.clone() }, - node_resolver, - Config { - require: true, - ..Default::default() - }, - Box::new(Hook), - ); - let mut entries = HashMap::default(); - entries.insert("main".to_string(), FileName::Real(file)); - - let mut bundles = bundler - .bundle(entries) - .context("failed to bundle javascript with swc")?; - // Since we only inserted one entry, there should only be one bundle in the output - let bundle = bundles - .pop() - .ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?; - bundle.module - } else { - let fm = cm.load_file(Path::new(&file)).expect("Failed to load file"); - - let lexer = Lexer::new( - Default::default(), - Default::default(), - SourceFileInput::from(&*fm), - None, - ); - let mut parser = Parser::new_from(lexer); - - parser.parse_module().map_err(|err| { - HANDLER.with(|handler| { - let mut error = err.into_diagnostic(handler); - // swc errors panic on drop if you don't cancel them - error.cancel(); - anyhow::anyhow!("{}", error.message()) - }) - })? - }; - - if minify { - module = swc_ecma_minifier::optimize( - std::mem::take(&mut module).into(), - cm.clone(), - None, - None, - &MinifyOptions { - rename: true, - compress: None, - mangle: None, - ..Default::default() - }, - &ExtraOptions { - unresolved_mark: Mark::new(), - top_level_mark: Mark::new(), - mangle_name_cache: None, - }, - ) - .expect_module(); - module.visit_mut_with(&mut fixer(None)); - } - - let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config::default().with_minify(minify), - cm: cm.clone(), - comments: None, - wr: Box::new(JsWriter::new(cm, "\n", write_to, None)), - }; - - emitter.emit_module(&module)?; - - Ok(()) -} - -struct PathLoader { - cm: Lrc, -} - -impl Load for PathLoader { - fn load(&self, file: &FileName) -> anyhow::Result { - let file = match file { - FileName::Real(v) => v, - _ => anyhow::bail!("Only real files are supported"), - }; - - let fm = self.cm.load_file(file)?; - - let module = HANDLER.with(|handler| { - parse_file_as_module( - &fm, - Syntax::Es(Default::default()), - Default::default(), - None, - &mut Vec::new(), - ) - .map_err(|err| { - let mut error = err.into_diagnostic(handler); - // swc errors panic on drop if you don't cancel them - error.cancel(); - anyhow::anyhow!("{}", error.message()) - }) - .context("Failed to parse javascript") - })?; - - Ok(ModuleData { - fm, - module, - helpers: Default::default(), - }) - } -} - -// Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302 -struct Hook; - -impl swc_bundler::Hook for Hook { - fn get_import_meta_props( - &self, - span: Span, - module_record: &ModuleRecord, - ) -> Result, Error> { - let file_name = module_record.file_name.to_string(); - - Ok(vec![ - KeyValueProp { - key: PropName::Ident(IdentName::new("url".into(), span)), - value: Box::new(Expr::Lit(Lit::Str(Str { - span, - raw: None, - value: file_name.into(), - }))), - }, - KeyValueProp { - key: PropName::Ident(IdentName::new("main".into(), span)), - value: Box::new(if module_record.is_entry { - Expr::Member(MemberExpr { - span, - obj: Box::new(Expr::MetaProp(MetaPropExpr { - span, - kind: MetaPropKind::ImportMeta, - })), - prop: MemberProp::Ident(IdentName::new("main".into(), span)), - }) - } else { - Expr::Lit(Lit::Bool(Bool { span, value: false })) - }), - }, - ]) - } -} +use manganis::JsAssetOptions; pub(crate) fn process_js( js_options: &JsAssetOptions, @@ -229,13 +234,13 @@ pub(crate) fn process_js( bundle: bool, ) -> anyhow::Result<()> { let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?); - if js_options.minified() { - if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) { - tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); - } else { - return Ok(()); - } - } + // if js_options.minified() { + // if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) { + // tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); + // } else { + // return Ok(()); + // } + // } let mut source_file = std::fs::File::open(source)?; std::io::copy(&mut source_file, &mut writer).with_context(|| { format!( diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 8274725398..c7c718e9bc 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -123,8 +123,11 @@ fn create_wasm_jump_table(original: &Path, patch: &Path) -> anyhow::Result anyhow::Result HashMap<&str, i32> { +fn collect_func_ifuncs<'a>(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap<&'a str, i32> { let mut name_to_ifunc_index = HashMap::new(); + // name -> index + // we want to export *all* these functions + let all_funcs = syms + .iter() + .flat_map(|sym| match sym { + SymbolInfo::Func { index, name, .. } => Some((name.unwrap(), *index)), + _ => None, + }) + .collect::>(); + + let mut ref_funcs = HashMap::new(); + + let mut offsets = HashMap::new(); + for el in m.elements.iter() { let ElementKind::Active { offset, .. } = &el.kind else { + tracing::info!("Skipping section: {:?} -> {:?}", el.name, el.kind); continue; }; @@ -155,24 +173,70 @@ fn collect_func_ifuncs(m: &Module) -> HashMap<&str, i32> { walrus::ConstExpr::Value(value) => match value { walrus::ir::Value::I32(idx) => *idx, walrus::ir::Value::I64(idx) => *idx as i32, - _ => continue, + _ => panic!(), }, // Globals are usually imports and thus don't add a specific offset // ie the ifunc table is offset by a global, so we don't need to push the offset out walrus::ConstExpr::Global(_) => 0, - walrus::ConstExpr::RefNull(_) => continue, - walrus::ConstExpr::RefFunc(_) => continue, + walrus::ConstExpr::RefNull(_) => panic!(), + walrus::ConstExpr::RefFunc(_) => panic!(), }; - if let ElementItems::Functions(ids) = &el.items { - for (idx, id) in ids.iter().enumerate() { - let name = m.funcs.get(*id).name.as_ref().unwrap(); - name_to_ifunc_index.insert(name.as_str(), offset + idx as i32); + match &el.items { + ElementItems::Functions(ids) => { + for (idx, id) in ids.iter().enumerate() { + offsets.insert(id.index(), offset + idx as i32); + } + } + ElementItems::Expressions(ref_type, const_exprs) => { + for e in const_exprs { + match e { + walrus::ConstExpr::Value(value) => panic!(), + walrus::ConstExpr::Global(id) => panic!(), + walrus::ConstExpr::RefNull(ref_type) => panic!(), + walrus::ConstExpr::RefFunc(id) => { + let f = m.funcs.get(*id); + ref_funcs.insert(id.index(), f.name.clone()); + tracing::warn!("Ref func name: {:?} {:?}", *id, f.name); + } + } + } + // tracing::info!("Indirect function table is not a function table: {const_exprs:?}"); + // let last = const_exprs.last().clone().unwrap(); + // for (name, i) in make_indirect { + // ids.push(idxs_to_ids[*i as usize]); + // } + } + } + } + + let mut missing = vec![]; + for (name, index) in all_funcs.iter() { + if let Some(offset) = offsets.get(&(*index as _)) { + name_to_ifunc_index.insert(*name, *offset); + } else { + missing.push((name, index)); + if let Some(shim) = ref_funcs.get(&(*index as _)) { + tracing::error!( + "Ref func was transformed! {:?} -> {:?} -> {:?}", + index, + shim, + all_funcs.iter().find(|(k, v)| **v == *index) + ); } } + // let offset = offsets.get(&(index as _)).unwrap(); } + tracing::info!("There are {} missing ifuncs", missing.len()); + tracing::info!("some are: {:#?}", &missing[0..missing.len().min(10)]); + + // name_to_ifunc_index.insert( + // m.funcs.get(*id).name.as_ref().unwrap().as_str(), + // offset + idx as i32, + // ); + name_to_ifunc_index } @@ -389,138 +453,26 @@ pub fn resolve_undefined( } } - // The loader host might want to know the address of various sections - // Let's add some symbols in the form of __SECTION_START_{SECTION_NAME} and __SECTION_END_{SECTION_NAME} - // such that dlsym can be used to find them. - // - // This will also be used by the program loader to identify the ASLR slide - for in_section in source.sections() { - // tracing::info!("Defining section header: {:?}", section); - // let sym = obj.section_symbol(section_id); - - let Ok(name) = in_section.name_bytes() else { - tracing::error!("Section has no name: {:?}", in_section); - continue; - }; - - if name != b"manganis" { - continue; - } - - let mut start = None; - for s in source.symbols() { - if s.section_index() == Some(in_section.index()) { - tracing::info!("Reading symbol header: {:?}", s); - if start.is_none() { - start = Some(s); - } - } - } - - // if let Some(s) = start { - // // import the symbol - // let id = obj.add_symbol(Symbol { - // name: format!("__SECTION_START_{}", s.name().unwrap()) - // .as_bytes() - // .to_vec(), - // value: 0, - // size: 0, - // kind: s.kind(), - // scope: SymbolScope::Dynamic, - // weak: false, - // section: SymbolSection::Section(in_section.index()), - // flags: object::SymbolFlags::None, - // }); - - // // Define a new symbol - - // // obj.add_symbol(Symbol { - // // name: format!("__SECTION_START_{}", s.name().unwrap_or_default()) - // // .as_bytes() - // // .to_vec(), - // // value: 0, - // // size: 0, - // // kind: (), - // // scope: (), - // // weak: (), - // // section: (), - // // flags: (), - // // }); - // } - - let kind = if in_section.kind() == SectionKind::Unknown { - SectionKind::Data - } else { - in_section.kind() - }; - - let section_id = obj.add_section( - in_section - .segment_name() - .unwrap() - .unwrap_or("") - .as_bytes() - .to_vec(), - name.to_vec(), - kind, - ); - let out_section = obj.section_mut(section_id); - out_section.flags = in_section.flags(); - - if out_section.is_bss() { - } else { - // obj.set_section_data(section_id, &[0_u8, 1, 2, 3, 4, 5, 6, 7], 4); - tracing::info!("Defining section header: {:?}", in_section); - let sym = obj.add_symbol(Symbol { - name: format!("__SECTION_START_{}", in_section.name().unwrap_or_default()) - .as_bytes() - .to_vec(), - value: 0, - size: 0, - kind: SymbolKind::Text, - scope: SymbolScope::Dynamic, - weak: false, - section: SymbolSection::Section(section_id), - flags: object::SymbolFlags::None, - }); - } - } - - // // "__DATA,manganis,regular,no_dead_strip".as_bytes().to_vec(), - // let sect = obj.add_section( - // "__DATA".as_bytes().to_vec(), - // "manganis".as_bytes().to_vec(), - // SectionKind::Data, - // ); - // let sym = obj.add_symbol(Symbol { - // name: format!("__SECTION_START_MANGANIS").as_bytes().to_vec(), - // value: 0, - // size: 0, - // kind: SymbolKind::Data, - // scope: SymbolScope::Dynamic, - // weak: false, - // section: SymbolSection::Section(sect), - // flags: object::SymbolFlags::None, - // }); - - // Write the object to a file - let bytes = obj.write()?; - Ok(bytes) + Ok(obj.write()?) } /// Prepares the base module before running wasm-bindgen. /// /// This tries to work around how wasm-bindgen works by intelligently promoting non-wasm-bindgen functions /// to the export table. +/// +/// It also moves all functions and memories to be callable indirectly. pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { - let mut pre_bindgen = walrus::Module::from_buffer(bytes)?; + let mut module = walrus::Module::from_buffer(bytes)?; - let bindgen_funcs = collect_all_wasm_bindgen_funcs(&pre_bindgen); + let bindgen_funcs = collect_all_wasm_bindgen_funcs(&module); // Due to monomorphizations, functions will get merged and multiple names will point to the same function. // Walrus loses this information, so we need to manually parse the names table to get the indices // and names of these functions. let raw_data = parse_bytes_to_data_segment(bytes)?; + let ifunc_map = collect_func_ifuncs(&module, &raw_data); + let ifunc_table_initialzer = module.elements.iter().last().unwrap().id(); // name -> index // we want to export *all* these functions @@ -532,34 +484,52 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { }) .collect::>(); - let index_to_func = pre_bindgen - .funcs - .iter() - .enumerate() - .collect::>(); + let index_to_func = module.funcs.iter().enumerate().collect::>(); - let mut already_exported = pre_bindgen + let mut already_exported = module .exports .iter() .map(|exp| exp.name.clone()) .chain( bindgen_funcs .iter() - .map(|id| pre_bindgen.funcs.get(*id).name.as_ref().unwrap().clone()), + .map(|id| module.funcs.get(*id).name.as_ref().unwrap().clone()), ) .collect::>(); - for (name, index) in all_funcs { + let make_indirect: Vec<_> = all_funcs + .iter() + .filter(|(name, id)| !ifunc_map.contains_key(*name)) + .collect(); + + for (&name, &index) in all_funcs.iter() { let func = index_to_func.get(&(index as usize)).unwrap(); if let FunctionKind::Local(_local) = &func.kind { if !already_exported.contains(name) { - pre_bindgen.exports.add(&name, func.id()); + module.exports.add(&name, func.id()); already_exported.insert(name.to_string()); } } } - Ok(pre_bindgen.emit_wasm()) + let idxs_to_ids = module.funcs.iter().map(|f| f.id()).collect::>(); + tracing::info!("Hoisting {} functions", make_indirect.len()); + match &mut module.elements.get_mut(ifunc_table_initialzer).items { + ElementItems::Functions(ids) => { + for (name, i) in make_indirect { + ids.push(idxs_to_ids[*i as usize]); + } + } + ElementItems::Expressions(ref_type, const_exprs) => { + panic!("Indirect function table is not a function table: {const_exprs:?}"); + // let last = const_exprs.last().clone().unwrap(); + // for (name, i) in make_indirect { + // ids.push(idxs_to_ids[*i as usize]); + // } + } + } + + Ok(module.emit_wasm()) } /// Collect all the wasm-bindgen functions in the module. We are going to make *everything* exported @@ -619,7 +589,8 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result let old: walrus::Module = walrus::Module::from_buffer(old_bytes)?; let mut new: walrus::Module = walrus::Module::from_buffer(new_bytes)?; - let ifunc_map = collect_func_ifuncs(&old); + let raw_data = parse_bytes_to_data_segment(&old_bytes)?; + let ifunc_map = collect_func_ifuncs(&old, &raw_data); let global_map = collect_global_map(&old); let mut mems = vec![]; @@ -628,24 +599,38 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result // Collect the GOT func/mem entries for t in new.imports.iter() { match t.module.as_str() { - "GOT.func" => funcs.push(( - t.id(), - *ifunc_map - .get(t.name.as_str()) - .unwrap_or_else(|| panic!("failed to find GOT.func: {}", t.name.as_str())), - )), + "GOT.func" => { + // funcs.push((t.id(), t.name.as_str())); + + funcs.push(( + t.id(), + *ifunc_map.get(t.name.as_str()).unwrap_or_else(|| { + // let exists = old + // .funcs + // .iter() + // .find(|f| f.name.as_deref().unwrap_or_default() == t.name) + // .map(|f| f.id()); + let exists = old.exports.get_func(t.name.as_str()); + panic!("failed to find GOT.func: {} -> {exists:?}", t.name.as_str()) + }), + )); + } "GOT.mem" => mems.push(t.id()), _ => {} } } - // Satisfies the GOT.func imports + // Satisfies the GOT.func imports. They exist as regular imports, but we need to make the indirect call for (imp_id, val) in funcs { + // for (import_id, name) in funcs { let imp = new.imports.get(imp_id); let global_id = match imp.kind { ImportKind::Global(id) => id, _ => todo!(), }; + // new.globals.get_mut(global_id).kind = + // walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32(val as i32))); + // new.imports.delete(imp_id); new.globals.get_mut(global_id).kind = walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32(val as i32))); new.imports.delete(imp_id); @@ -656,7 +641,13 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result for mem in mems { let imp = new.imports.get(mem); let name = format!("GOT.data.internal.{}", imp.name); - let val = global_map.get(name.as_str()).unwrap(); + let val = global_map.get(name.as_str()).unwrap_or_else(|| { + let non_got = global_map.get(name.as_str()); + panic!( + "failed to find GOT.mem: {} -> non got: {non_got:?}", + name.as_str() + ) + }); let global_id = match imp.kind { ImportKind::Global(id) => id, _ => todo!(), @@ -715,6 +706,15 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result> { Ok(symbols) } +struct SymbolMap<'a> { + symbols: Vec>, +} + +enum Node { + Function(FunctionId), + Data(usize), +} + async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, out_path: PathBuf) { let mut object = ObjectDiff::new().unwrap(); object.load().unwrap(); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index d2682b42fb..1cb06afeb4 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -749,7 +749,7 @@ impl BuildRequest { let mut cmd = self.build_command(ctx)?; tracing::debug!("Executing cargo..."); - tracing::trace!(dx_src = ?TraceSrc::Build, "Rust cargo args: {:#?}", cmd); + // tracing::trace!(dx_src = ?TraceSrc::Build, "Rust cargo args: {:#?}", cmd); // Extract the unit count of the crate graph so build_cargo has more accurate data // "Thin" builds only build the final exe, so we only need to build one crate @@ -1357,6 +1357,8 @@ impl BuildRequest { "--no-demangle".to_string(), "--no-entry".to_string(), "--pie".to_string(), + "--whole-archive".to_string(), + "--no-gc-sections".to_string(), "--experimental-pic".to_string(), ]); } diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 7cde3ee5af..83ebfd8e10 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -1408,7 +1408,7 @@ Finally, call `.build()` to create the instance of `{name}`. let assignments = self.fields.iter().map(|field| { let name = &field.name; if let Some(extends_vec) = field.extends_vec_ident() { - quote!{ + quote! { let mut #name = #helper_trait_name::into_value(#name, || ::core::default::Default::default()); #name.extend(self.#extends_vec); } @@ -1416,13 +1416,13 @@ Finally, call `.build()` to create the instance of `{name}`. // If field has `into`, apply it to the default value. // Ignore any blank defaults as it causes type inference errors. let is_default = *default == parse_quote!(::core::default::Default::default()); - let mut into = quote!{}; + let mut into = quote! {}; if !is_default { if field.builder_attr.auto_into { - into = quote!{ .into() } + into = quote! { .into() } } else if field.builder_attr.auto_to_string { - into = quote!{ .to_string() } + into = quote! { .to_string() } } } diff --git a/packages/core/README.md b/packages/core/README.md index a4b80f5000..c7a7e0526a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -65,7 +65,7 @@ use dioxus::prelude::*; // First, declare a root component fn app() -> Element { - rsx!{ + rsx! { div { "hello world" } } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 7d7276d984..3313f1345b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -785,6 +785,28 @@ impl Drop for VirtualDom { /// Yield control back to the async scheduler. This is used to give the scheduler a chance to run other pending work. Or cancel the task if the client has disconnected. async fn yield_now() { + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let abc = 123; + let mut yielded = false; std::future::poll_fn::<(), _>(move |cx| { if !yielded { diff --git a/packages/dioxus-lib/README.md b/packages/dioxus-lib/README.md index eb28cb7b16..bd8f85e181 100644 --- a/packages/dioxus-lib/README.md +++ b/packages/dioxus-lib/README.md @@ -94,7 +94,7 @@ The `rsx!` macro is what generates the `Element` that our components return. ```rust, ignore #[component] fn Example() -> Element { - rsx!{ "hello world" } + rsx! { "hello world" } } ``` diff --git a/packages/signals/docs/memo.md b/packages/signals/docs/memo.md index e812fdf522..867f23f9b9 100644 --- a/packages/signals/docs/memo.md +++ b/packages/signals/docs/memo.md @@ -74,7 +74,7 @@ let doubled = use_resource(move || async move { double_me_async(&halved).await }); -rsx!{ +rsx! { "{doubled:?}" button { onclick: move |_| { @@ -104,7 +104,7 @@ let doubled = use_resource(move || async move { double_me_async(halved).await; }); -rsx!{ +rsx! { "{doubled:?}" button { onclick: move |_| { diff --git a/packages/signals/docs/signals.md b/packages/signals/docs/signals.md index bf5f025eac..961a427de0 100644 --- a/packages/signals/docs/signals.md +++ b/packages/signals/docs/signals.md @@ -102,7 +102,7 @@ use_future(move || async move { double_me_async(&mut write).await; }); -rsx!{ +rsx! { // This read may panic because the write is still active while the future is waiting for the async work to finish "{signal}" }; diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 173e490235..d363199b24 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -110,7 +110,7 @@ macro_rules! read_impls { ),+ )? ) => { - $crate::fmt_impls!{ + $crate::fmt_impls! { $ty< T $( @@ -123,7 +123,7 @@ macro_rules! read_impls { $($extra_bound_ty: $extra_bound),* )? } - $crate::eq_impls!{ + $crate::eq_impls! { $ty< T $( diff --git a/packages/ssr/README.md b/packages/ssr/README.md index a7d1a76c66..5290fa6880 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -20,7 +20,7 @@ Dioxus SSR provides utilities to render Dioxus components to valid HTML. Once re ```rust # use dioxus::prelude::*; fn app() -> Element { - rsx!{ + rsx! { div {"hello world!"} } } @@ -38,7 +38,7 @@ The simplest example is to simply render some `rsx!` nodes to HTML. This can be ```rust, no_run # use dioxus::prelude::*; -let content = dioxus_ssr::render_element(rsx!{ +let content = dioxus_ssr::render_element(rsx! { div { for i in 0..5 { "Number: {i}" From 27e58bc4bac43516724c0683d5212b749ed6ace8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:31:54 -0700 Subject: [PATCH 146/301] fix: use workspace for krate discovery --- packages/cli/src/workspace.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index cb283aa0b8..f67f16adc3 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -34,7 +34,8 @@ impl Workspace { tracing::debug!("Loading workspace!"); let cmd = Cmd::new(); - let builder = krates::Builder::new(); + let mut builder = krates::Builder::new(); + builder.workspace(true); let krates = builder .build(cmd, |_| {}) .context("Failed to run cargo metadata")?; From cc2a214c35f50a7bcb72db2b6293592b23f77aaa Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:32:28 -0700 Subject: [PATCH 147/301] fix panic logging on serve by placing it *after* the logs --- packages/cli/src/cli/serve.rs | 94 +++++++++++++++++++++++++++++++++-- packages/cli/src/logging.rs | 14 ++++-- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 0e240493a2..ccbed04292 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -1,5 +1,9 @@ +use std::{backtrace::Backtrace, panic::AssertUnwindSafe}; + use super::{chained::ChainedCommand, *}; -use crate::{AddressArguments, BuildArgs, PROFILE_SERVER}; +use crate::{AddressArguments, BuildArgs, TraceController, PROFILE_SERVER}; +use futures_util::FutureExt; +use once_cell::sync::OnceCell; use target_lexicon::Triple; /// Serve the project @@ -136,9 +140,93 @@ impl ServeArgs { /// /// Make sure not to do any intermediate logging since our tracing infra has now enabled much /// higher log levels + /// + /// We also set up proper panic handling since the TUI has a tendency to corrupt the terminal. pub(crate) async fn serve(self) -> Result { - crate::serve::serve_all(self).await?; - Ok(StructuredOutput::Success) + if std::env::var("RUST_BACKTRACE").is_err() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + struct SavedLocation { + file: String, + line: u32, + column: u32, + } + static BACKTRACE: OnceCell<(Backtrace, Option)> = OnceCell::new(); + + // We *don't* want printing here, since it'll break the tui and log ordering. + // + // We *will* re-emit the panic after we've drained the tracer, so our panic hook will simply capture the panic + // and save it. + std::panic::set_hook(Box::new(move |panic_info| { + _ = BACKTRACE.set(( + Backtrace::capture(), + panic_info.location().map(|l| SavedLocation { + file: l.file().to_string(), + line: l.line(), + column: l.column(), + }), + )); + })); + + let interactive = self.is_interactive_tty(); + + // Redirect all logging the cli logger - if there's any pending after a panic, we flush it + let mut tracer = TraceController::redirect(interactive); + + let res = AssertUnwindSafe(crate::serve::serve_all(self, &mut tracer)) + .catch_unwind() + .await; + + // Kill the screen so we don't ruin the terminal + _ = crate::serve::Output::remote_shutdown(interactive); + + // And drain the tracer as regular messages. All messages will be logged (including traces) + // and then we can print the panic message + if !matches!(res, Ok(Ok(_))) { + tracer.shutdown_panic(); + } + + match res { + Ok(Ok(_res)) => Ok(StructuredOutput::Success), + Ok(Err(e)) => Err(e), + Err(panic_err) => { + // And then print the panic itself. + let as_str = if let Some(p) = panic_err.downcast_ref::() { + p.as_ref() + } else if let Some(p) = panic_err.downcast_ref::<&str>() { + p.as_ref() + } else { + "" + }; + + // Attempt to emulate the default panic hook + let message = BACKTRACE + .get() + .map(|(back, location)| { + let location_display = location + .as_ref() + .map(|l| format!("{}:{}:{}", l.file, l.line, l.column)) + .unwrap_or_else(|| "".to_string()); + + let mut backtrace_display = back.to_string(); + + // split at the line that ends with ___rust_try for short backtraces + if std::env::var("RUST_BACKTRACE") == Ok("1".to_string()) { + backtrace_display = backtrace_display + .split(" ___rust_try\n") + .next() + .map(|f| format!("{f} ___rust_try")) + .unwrap_or_default(); + } + + format!("dx serve panicked at {location_display}\n{as_str}\n{backtrace_display} ___rust_try") + }) + .unwrap_or_else(|| format!("dx serve panicked: {as_str}")); + + Err(crate::error::Error::CapturedPanic(message)) + } + } } /// Check if the server is running in interactive mode. This involves checking the terminal as well diff --git a/packages/cli/src/logging.rs b/packages/cli/src/logging.rs index 7f8b90d795..7091385631 100644 --- a/packages/cli/src/logging.rs +++ b/packages/cli/src/logging.rs @@ -154,19 +154,23 @@ impl TraceController { ServeUpdate::TracingLog { log } } -} -impl Drop for TraceController { - fn drop(&mut self) { + pub(crate) fn shutdown_panic(&mut self) { TUI_ACTIVE.store(false, Ordering::Relaxed); // re-emit any remaining messages while let Ok(Some(msg)) = self.tui_rx.try_next() { - let contents = match msg.content { + let content = match msg.content { TraceContent::Text(text) => text, TraceContent::Cargo(msg) => msg.message.to_string(), }; - tracing::error!("{}", contents); + match msg.level { + Level::ERROR => tracing::error!("{content}"), + Level::WARN => tracing::warn!("{content}"), + Level::INFO => tracing::info!("{content}"), + Level::DEBUG => tracing::debug!("{content}"), + Level::TRACE => tracing::trace!("{content}"), + } } } } From c7f554313b8081529997b7aba3888e88cc3209b8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:34:16 -0700 Subject: [PATCH 148/301] reorder logging to be sensible on success --- packages/cli/src/error.rs | 4 ++++ packages/cli/src/main.rs | 3 ++- packages/cli/src/serve/mod.rs | 7 ++----- packages/cli/src/serve/output.rs | 16 ++++++---------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/error.rs b/packages/cli/src/error.rs index 5da4852be6..56c1facefd 100644 --- a/packages/cli/src/error.rs +++ b/packages/cli/src/error.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use thiserror::Error as ThisError; pub(crate) type Result = std::result::Result; @@ -36,6 +37,9 @@ pub(crate) enum Error { #[error("Unsupported feature: {0}")] UnsupportedFeature(String), + #[error("{0}")] + CapturedPanic(String), + #[error("Failed to render template: {0}")] TemplateParse(#[from] handlebars::RenderError), diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index d72f78d31a..51cd97cd09 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -68,8 +68,9 @@ async fn main() { tracing::debug!(json = ?output); } Err(err) => { + eprintln!("{err}"); + tracing::error!( - ?err, json = ?StructuredOutput::Error { message: format!("{err:?}"), }, diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index c250ba7667..0f3d44d0b8 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -35,14 +35,11 @@ pub(crate) use update::*; /// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly. /// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server /// to a dynamic one on the fly. -pub(crate) async fn serve_all(args: ServeArgs) -> Result<()> { - // Redirect all logging the cli logger - let mut tracer = TraceController::redirect(args.is_interactive_tty()); - +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 mut screen = Output::start(args.is_interactive_tty()).await?; let mut builder = AppServer::start(args).await?; let mut devserver = WebServer::start(&builder)?; + let mut screen = Output::start(builder.interactive).await?; // This is our default splash screen. We might want to make this a fancier splash screen in the future // Also, these commands might not be the most important, but it's all we've got enabled right now diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 2b052c1ac6..801adf1706 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -106,15 +106,6 @@ impl Output { /// This is meant to be paired with "shutdown" to restore the terminal to its original state. fn startup(&mut self) -> io::Result<()> { if self.interactive { - // set the panic hook to fix the terminal in the event of a panic - // The terminal might be left in a wonky state if a panic occurs, and we don't want it to be completely broken - let original_hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |info| { - _ = disable_raw_mode(); - _ = stdout().execute(Show); - original_hook(info); - })); - // Check if writing the terminal is going to block infinitely. // If it does, we should disable interactive mode. This ensures we work with programs like `bg` // which suspend the process and cause us to block when writing output. @@ -176,7 +167,12 @@ impl Output { /// Call the shutdown functions that might mess with the terminal settings - see the related code /// in "startup" for more details about what we need to unset pub(crate) fn shutdown(&self) -> io::Result<()> { - if self.interactive { + Self::remote_shutdown(self.interactive)?; + Ok(()) + } + + pub(crate) fn remote_shutdown(interactive: bool) -> io::Result<()> { + if interactive { stdout() .execute(Show)? .execute(DisableFocusChange)? From da73f69954182b38fb753fae907b59d4e204cc7c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:35:22 -0700 Subject: [PATCH 149/301] bring back swc --- packages/cli-opt/Cargo.toml | 54 ++--- packages/cli-opt/src/js.rs | 459 ++++++++++++++++++------------------ 2 files changed, 254 insertions(+), 259 deletions(-) diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index 062696a610..06c3210194 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -39,31 +39,31 @@ lightningcss = { version = "1.0.0-alpha.63", features = ["browserslist", "into_o grass = "0.13.4" codemap = "0.1.3" -# # Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates -# swc_allocator = { version = "=2.0.0", default-features = false } -# swc_atoms = { version = "=3.0.2", default-features = false } -# swc_bundler = { version = "=7.0.0", default-features = false } -# swc_cached = { version = "=1.0.0", default-features = false } -# swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false } -# swc_config = { version = "=1.0.0", default-features = false } -# swc_config_macro = { version = "=1.0.0", default-features = false } -# swc_ecma_ast = { version = "=5.0.1", default-features = false } -# swc_ecma_codegen = { version = "=5.0.1", default-features = false } -# swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false } -# swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false } -# swc_ecma_minifier = { version = "=7.0.1", default-features = false } -# swc_ecma_parser = { version = "=6.0.2", default-features = false } -# swc_ecma_transforms_base = { version = "=7.0.0", default-features = false } -# swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false } -# swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false } -# swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false } -# swc_ecma_utils = { version = "=7.0.0", default-features = false } -# swc_ecma_visit = { version = "=5.0.0", default-features = false } -# swc_eq_ignore_macros = { version = "=1.0.0", default-features = false } -# swc_fast_graph = { version = "=6.0.0", default-features = false } -# swc_graph_analyzer = { version = "=5.0.0", default-features = false } -# swc_macros_common = { version = "=1.0.0", default-features = false } -# swc_parallel = { version = "=1.0.1", default-features = false } -# swc_timer = { version = "=1.0.0", default-features = false } -# swc_visit = { version = "=2.0.0", default-features = false } +# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates +swc_allocator = { version = "=2.0.0", default-features = false } +swc_atoms = { version = "=3.0.2", default-features = false } +swc_bundler = { version = "=7.0.0", default-features = false } +swc_cached = { version = "=1.0.0", default-features = false } +swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false } +swc_config = { version = "=1.0.0", default-features = false } +swc_config_macro = { version = "=1.0.0", default-features = false } +swc_ecma_ast = { version = "=5.0.1", default-features = false } +swc_ecma_codegen = { version = "=5.0.1", default-features = false } +swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false } +swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false } +swc_ecma_minifier = { version = "=7.0.1", default-features = false } +swc_ecma_parser = { version = "=6.0.2", default-features = false } +swc_ecma_transforms_base = { version = "=7.0.0", default-features = false } +swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false } +swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false } +swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false } +swc_ecma_utils = { version = "=7.0.0", default-features = false } +swc_ecma_visit = { version = "=5.0.0", default-features = false } +swc_eq_ignore_macros = { version = "=1.0.0", default-features = false } +swc_fast_graph = { version = "=6.0.0", default-features = false } +swc_graph_analyzer = { version = "=5.0.0", default-features = false } +swc_macros_common = { version = "=1.0.0", default-features = false } +swc_parallel = { version = "=1.0.1", default-features = false } +swc_timer = { version = "=1.0.0", default-features = false } +swc_visit = { version = "=2.0.0", default-features = false } browserslist-rs = { version = "=0.16.0" } diff --git a/packages/cli-opt/src/js.rs b/packages/cli-opt/src/js.rs index 1d12e2f190..1e79067a45 100644 --- a/packages/cli-opt/src/js.rs +++ b/packages/cli-opt/src/js.rs @@ -1,231 +1,226 @@ -// use std::path::Path; -// use std::path::PathBuf; - -// use anyhow::Context; -// use manganis_core::JsAssetOptions; -// use swc_common::errors::Emitter; -// use swc_common::errors::Handler; -// use swc_common::input::SourceFileInput; -// use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions}; -// use swc_ecma_parser::lexer::Lexer; -// use swc_ecma_parser::Parser; -// use swc_ecma_transforms_base::fixer::fixer; -// use swc_ecma_visit::VisitMutWith; - -// use std::collections::HashMap; - -// use anyhow::Error; -// use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord}; -// use swc_common::{ -// errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS, -// }; -// use swc_ecma_ast::*; -// use swc_ecma_codegen::text_writer::JsWriter; -// use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv}; -// use swc_ecma_parser::{parse_file_as_module, Syntax}; - -// struct TracingEmitter; - -// impl Emitter for TracingEmitter { -// fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { -// match db.level { -// swc_common::errors::Level::Bug -// | swc_common::errors::Level::Fatal -// | swc_common::errors::Level::PhaseFatal -// | swc_common::errors::Level::Error => tracing::error!("{}", db.message()), -// swc_common::errors::Level::Warning -// | swc_common::errors::Level::FailureNote -// | swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()), -// swc_common::errors::Level::Note | swc_common::errors::Level::Help => { -// tracing::trace!("{}", db.message()) -// } -// } -// } -// } - -// fn bundle_js_to_writer( -// file: PathBuf, -// bundle: bool, -// minify: bool, -// write_to: &mut impl std::io::Write, -// ) -> anyhow::Result<()> { -// let globals = Globals::new(); -// let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default()); -// GLOBALS.set(&globals, || { -// HANDLER.set(&handler, || { -// bundle_js_to_writer_inside_handler(&globals, file, bundle, minify, write_to) -// }) -// }) -// } - -// fn bundle_js_to_writer_inside_handler( -// globals: &Globals, -// file: PathBuf, -// bundle: bool, -// minify: bool, -// write_to: &mut impl std::io::Write, -// ) -> anyhow::Result<()> { -// let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); -// let mut module = if bundle { -// let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true); -// let mut bundler = Bundler::new( -// globals, -// cm.clone(), -// PathLoader { cm: cm.clone() }, -// node_resolver, -// Config { -// require: true, -// ..Default::default() -// }, -// Box::new(Hook), -// ); -// let mut entries = HashMap::default(); -// entries.insert("main".to_string(), FileName::Real(file)); - -// let mut bundles = bundler -// .bundle(entries) -// .context("failed to bundle javascript with swc")?; -// // Since we only inserted one entry, there should only be one bundle in the output -// let bundle = bundles -// .pop() -// .ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?; -// bundle.module -// } else { -// let fm = cm.load_file(Path::new(&file)).expect("Failed to load file"); - -// let lexer = Lexer::new( -// Default::default(), -// Default::default(), -// SourceFileInput::from(&*fm), -// None, -// ); -// let mut parser = Parser::new_from(lexer); - -// parser.parse_module().map_err(|err| { -// HANDLER.with(|handler| { -// let mut error = err.into_diagnostic(handler); -// // swc errors panic on drop if you don't cancel them -// error.cancel(); -// anyhow::anyhow!("{}", error.message()) -// }) -// })? -// }; - -// if minify { -// module = swc_ecma_minifier::optimize( -// std::mem::take(&mut module).into(), -// cm.clone(), -// None, -// None, -// &MinifyOptions { -// rename: true, -// compress: None, -// mangle: None, -// ..Default::default() -// }, -// &ExtraOptions { -// unresolved_mark: Mark::new(), -// top_level_mark: Mark::new(), -// mangle_name_cache: None, -// }, -// ) -// .expect_module(); -// module.visit_mut_with(&mut fixer(None)); -// } - -// let mut emitter = swc_ecma_codegen::Emitter { -// cfg: swc_ecma_codegen::Config::default().with_minify(minify), -// cm: cm.clone(), -// comments: None, -// wr: Box::new(JsWriter::new(cm, "\n", write_to, None)), -// }; - -// emitter.emit_module(&module)?; - -// Ok(()) -// } - -// struct PathLoader { -// cm: Lrc, -// } - -// impl Load for PathLoader { -// fn load(&self, file: &FileName) -> anyhow::Result { -// let file = match file { -// FileName::Real(v) => v, -// _ => anyhow::bail!("Only real files are supported"), -// }; - -// let fm = self.cm.load_file(file)?; - -// let module = HANDLER.with(|handler| { -// parse_file_as_module( -// &fm, -// Syntax::Es(Default::default()), -// Default::default(), -// None, -// &mut Vec::new(), -// ) -// .map_err(|err| { -// let mut error = err.into_diagnostic(handler); -// // swc errors panic on drop if you don't cancel them -// error.cancel(); -// anyhow::anyhow!("{}", error.message()) -// }) -// .context("Failed to parse javascript") -// })?; - -// Ok(ModuleData { -// fm, -// module, -// helpers: Default::default(), -// }) -// } -// } - -// // Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302 -// struct Hook; - -// impl swc_bundler::Hook for Hook { -// fn get_import_meta_props( -// &self, -// span: Span, -// module_record: &ModuleRecord, -// ) -> Result, Error> { -// let file_name = module_record.file_name.to_string(); - -// Ok(vec![ -// KeyValueProp { -// key: PropName::Ident(IdentName::new("url".into(), span)), -// value: Box::new(Expr::Lit(Lit::Str(Str { -// span, -// raw: None, -// value: file_name.into(), -// }))), -// }, -// KeyValueProp { -// key: PropName::Ident(IdentName::new("main".into(), span)), -// value: Box::new(if module_record.is_entry { -// Expr::Member(MemberExpr { -// span, -// obj: Box::new(Expr::MetaProp(MetaPropExpr { -// span, -// kind: MetaPropKind::ImportMeta, -// })), -// prop: MemberProp::Ident(IdentName::new("main".into(), span)), -// }) -// } else { -// Expr::Lit(Lit::Bool(Bool { span, value: false })) -// }), -// }, -// ]) -// } -// } - use std::path::Path; +use std::path::PathBuf; use anyhow::Context; -use manganis::JsAssetOptions; +use manganis_core::JsAssetOptions; +use swc_common::errors::Emitter; +use swc_common::errors::Handler; +use swc_common::input::SourceFileInput; +use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions}; +use swc_ecma_parser::lexer::Lexer; +use swc_ecma_parser::Parser; +use swc_ecma_transforms_base::fixer::fixer; +use swc_ecma_visit::VisitMutWith; + +use std::collections::HashMap; + +use anyhow::Error; +use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord}; +use swc_common::{ + errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS, +}; +use swc_ecma_ast::*; +use swc_ecma_codegen::text_writer::JsWriter; +use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv}; +use swc_ecma_parser::{parse_file_as_module, Syntax}; + +struct TracingEmitter; + +impl Emitter for TracingEmitter { + fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { + match db.level { + swc_common::errors::Level::Bug + | swc_common::errors::Level::Fatal + | swc_common::errors::Level::PhaseFatal + | swc_common::errors::Level::Error => tracing::error!("{}", db.message()), + swc_common::errors::Level::Warning + | swc_common::errors::Level::FailureNote + | swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()), + swc_common::errors::Level::Note | swc_common::errors::Level::Help => { + tracing::trace!("{}", db.message()) + } + } + } +} + +fn bundle_js_to_writer( + file: PathBuf, + bundle: bool, + minify: bool, + write_to: &mut impl std::io::Write, +) -> anyhow::Result<()> { + let globals = Globals::new(); + let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default()); + GLOBALS.set(&globals, || { + HANDLER.set(&handler, || { + bundle_js_to_writer_inside_handler(&globals, file, bundle, minify, write_to) + }) + }) +} + +fn bundle_js_to_writer_inside_handler( + globals: &Globals, + file: PathBuf, + bundle: bool, + minify: bool, + write_to: &mut impl std::io::Write, +) -> anyhow::Result<()> { + let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let mut module = if bundle { + let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true); + let mut bundler = Bundler::new( + globals, + cm.clone(), + PathLoader { cm: cm.clone() }, + node_resolver, + Config { + require: true, + ..Default::default() + }, + Box::new(Hook), + ); + let mut entries = HashMap::default(); + entries.insert("main".to_string(), FileName::Real(file)); + + let mut bundles = bundler + .bundle(entries) + .context("failed to bundle javascript with swc")?; + // Since we only inserted one entry, there should only be one bundle in the output + let bundle = bundles + .pop() + .ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?; + bundle.module + } else { + let fm = cm.load_file(Path::new(&file)).expect("Failed to load file"); + + let lexer = Lexer::new( + Default::default(), + Default::default(), + SourceFileInput::from(&*fm), + None, + ); + let mut parser = Parser::new_from(lexer); + + parser.parse_module().map_err(|err| { + HANDLER.with(|handler| { + let mut error = err.into_diagnostic(handler); + // swc errors panic on drop if you don't cancel them + error.cancel(); + anyhow::anyhow!("{}", error.message()) + }) + })? + }; + + if minify { + module = swc_ecma_minifier::optimize( + std::mem::take(&mut module).into(), + cm.clone(), + None, + None, + &MinifyOptions { + rename: true, + compress: None, + mangle: None, + ..Default::default() + }, + &ExtraOptions { + unresolved_mark: Mark::new(), + top_level_mark: Mark::new(), + mangle_name_cache: None, + }, + ) + .expect_module(); + module.visit_mut_with(&mut fixer(None)); + } + + let mut emitter = swc_ecma_codegen::Emitter { + cfg: swc_ecma_codegen::Config::default().with_minify(minify), + cm: cm.clone(), + comments: None, + wr: Box::new(JsWriter::new(cm, "\n", write_to, None)), + }; + + emitter.emit_module(&module)?; + + Ok(()) +} + +struct PathLoader { + cm: Lrc, +} + +impl Load for PathLoader { + fn load(&self, file: &FileName) -> anyhow::Result { + let file = match file { + FileName::Real(v) => v, + _ => anyhow::bail!("Only real files are supported"), + }; + + let fm = self.cm.load_file(file)?; + + let module = HANDLER.with(|handler| { + parse_file_as_module( + &fm, + Syntax::Es(Default::default()), + Default::default(), + None, + &mut Vec::new(), + ) + .map_err(|err| { + let mut error = err.into_diagnostic(handler); + // swc errors panic on drop if you don't cancel them + error.cancel(); + anyhow::anyhow!("{}", error.message()) + }) + .context("Failed to parse javascript") + })?; + + Ok(ModuleData { + fm, + module, + helpers: Default::default(), + }) + } +} + +// Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302 +struct Hook; + +impl swc_bundler::Hook for Hook { + fn get_import_meta_props( + &self, + span: Span, + module_record: &ModuleRecord, + ) -> Result, Error> { + let file_name = module_record.file_name.to_string(); + + Ok(vec![ + KeyValueProp { + key: PropName::Ident(IdentName::new("url".into(), span)), + value: Box::new(Expr::Lit(Lit::Str(Str { + span, + raw: None, + value: file_name.into(), + }))), + }, + KeyValueProp { + key: PropName::Ident(IdentName::new("main".into(), span)), + value: Box::new(if module_record.is_entry { + Expr::Member(MemberExpr { + span, + obj: Box::new(Expr::MetaProp(MetaPropExpr { + span, + kind: MetaPropKind::ImportMeta, + })), + prop: MemberProp::Ident(IdentName::new("main".into(), span)), + }) + } else { + Expr::Lit(Lit::Bool(Bool { span, value: false })) + }), + }, + ]) + } +} pub(crate) fn process_js( js_options: &JsAssetOptions, @@ -234,13 +229,13 @@ pub(crate) fn process_js( bundle: bool, ) -> anyhow::Result<()> { let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?); - // if js_options.minified() { - // if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) { - // tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); - // } else { - // return Ok(()); - // } - // } + if js_options.minified() { + if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) { + tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); + } else { + return Ok(()); + } + } let mut source_file = std::fs::File::open(source)?; std::io::copy(&mut source_file, &mut writer).with_context(|| { format!( From 0f36512668668bd6bd3a32109916ca7fdc5cabcf Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:35:51 -0700 Subject: [PATCH 150/301] ws cruft --- .gitignore | 3 +++ .vscode/settings.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index d087576ddd..5605d470d0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ node_modules/ # ignore the output of tmps tmp/ bundle/ + +# in debugging we frequently dump wasm to wat with `wasm-tools print` +*.wat diff --git a/.vscode/settings.json b/.vscode/settings.json index df64c95194..a9b3a0b4f1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,9 @@ // "rust-analyzer.check.workspace": false, "rust-analyzer.cargo.features": "all", "rust-analyzer.check.features": "all", + "rust-analyzer.cargo.extraArgs": [ + "--tests" + ], // "rust-analyzer.check.allTargets": true, // we don't want the formatter to kick in while we're working on dioxus itself "dioxus.formatOnSave": "disabled", From d150b47292e74db60b269143035f4955bdb5502a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:41:10 -0700 Subject: [PATCH 151/301] small patches to relocs in loader --- packages/cli/src/build/request.rs | 10 ++++------ packages/subsecond/subsecond/src/lib.rs | 4 ++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 1cb06afeb4..706ff57abf 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -2698,10 +2698,8 @@ impl BuildRequest { // Locate the output of the build files and the bindgen output // We'll fill these in a second if they don't already exist let bindgen_outdir = self.wasm_bindgen_out_dir(); - let prebindgen = exe; let post_bindgen_wasm = self.wasm_bindgen_wasm_output_file(); let should_bundle_split: bool = self.wasm_split; - let rustc_exe = exe.with_extension("wasm"); let bindgen_version = self .wasm_bindgen_version() .expect("this should have been checked by tool verification"); @@ -2711,9 +2709,9 @@ impl BuildRequest { // Lift the internal functions to exports if ctx.mode == BuildMode::Fat { - let unprocessed = std::fs::read(&prebindgen)?; + let unprocessed = std::fs::read(&exe)?; let all_exported_bytes = crate::build::prepare_wasm_base_module(&unprocessed).unwrap(); - std::fs::write(&rustc_exe, all_exported_bytes)?; + std::fs::write(&exe, all_exported_bytes)?; } // Prepare our configuration @@ -2747,7 +2745,7 @@ impl BuildRequest { tracing::debug!(dx_src = ?TraceSrc::Bundle, "Running wasm-bindgen"); let start = std::time::Instant::now(); WasmBindgen::new(&bindgen_version) - .input_path(&rustc_exe) + .input_path(&exe) .target("web") .debug(keep_debug) .demangle(demangle) @@ -2777,7 +2775,7 @@ impl BuildRequest { // Load the contents of these binaries since we need both of them // We're going to use the default makeLoad glue from wasm-split - let original = std::fs::read(&prebindgen)?; + let original = std::fs::read(&exe)?; let bindgened = std::fs::read(&post_bindgen_wasm)?; let mut glue = wasm_split_cli::MAKE_LOAD_JS.to_string(); diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index f6d6cc6e9f..fa771d70d6 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -523,6 +523,10 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> { .unwrap() .unchecked_into::(); func.call0(&JsValue::undefined()).unwrap(); + let func = Reflect::get(&exports, &"__wasm_apply_global_relocs".into()) + .unwrap() + .unchecked_into::(); + func.call0(&JsValue::undefined()).unwrap(); unsafe { commit_patch(table) }; }); From aa0ca3ecf5801973fdddc3a4092d5b92eb843383 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:41:49 -0700 Subject: [PATCH 152/301] bump krates --- Cargo.lock | 904 ++++++++++++++++++++++++++++++++++++---- Cargo.toml | 1 + packages/cli/Cargo.toml | 3 +- 3 files changed, 829 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e95b93f1c4..2ac9430c86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,19 +112,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.28.1", + "gimli 0.31.1", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -459,7 +453,7 @@ dependencies = [ "scroll", "security-framework 2.11.1", "security-framework-sys", - "semver", + "semver 1.0.26", "serde", "serde_json", "serde_yaml", @@ -683,6 +677,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ast_node" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.100", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -1041,6 +1047,17 @@ dependencies = [ "terminal-prompt", ] +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -1313,17 +1330,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", - "object 0.32.2", + "miniz_oxide", + "object 0.36.7", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -1375,6 +1392,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "better_scoped_tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664" +dependencies = [ + "scoped-tls", +] + [[package]] name = "bigdecimal" version = "0.4.8" @@ -1797,6 +1823,9 @@ name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytecheck" @@ -1805,7 +1834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", - "ptr_meta", + "ptr_meta 0.1.4", "simdutf8", ] @@ -2000,7 +2029,7 @@ dependencies = [ "remove_dir_all", "rhai", "sanitize-filename", - "semver", + "semver 1.0.26", "serde", "tempfile", "thiserror 2.0.12", @@ -2024,7 +2053,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f905f68f8cb8a8182592d9858a5895360f0a5b08b6901fdb10498fb91829804" dependencies = [ - "semver", + "semver 1.0.26", "serde", "serde-untagged", "serde-value", @@ -2042,7 +2071,7 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver", + "semver 1.0.26", "serde", "serde_json", "thiserror 2.0.12", @@ -2155,9 +2184,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.17.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +checksum = "15b9a064fcc3a1cd4077688b2a1951e0884dcc098a06610f21d0413ccd687e67" dependencies = [ "smallvec", ] @@ -2799,15 +2828,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog 1.1.1", +] + [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ - "crc-catalog", + "crc-catalog 2.4.0", ] +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crc-catalog" version = "2.4.0" @@ -3123,7 +3167,7 @@ dependencies = [ "digest", "fiat-crypto 0.2.9", "rand_core 0.6.4", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -3319,6 +3363,16 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "depinfo" version = "0.1.0" @@ -3398,7 +3452,7 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.1", "syn 2.0.100", ] @@ -3580,6 +3634,7 @@ dependencies = [ "axum 0.8.3", "axum-extra", "axum-server", + "backtrace", "brotli", "built", "cargo-config2", @@ -3705,6 +3760,32 @@ dependencies = [ "rayon", "serde", "serde_json", + "swc_allocator", + "swc_atoms", + "swc_bundler", + "swc_cached", + "swc_common", + "swc_config", + "swc_config_macro", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_codegen_macros", + "swc_ecma_loader", + "swc_ecma_minifier", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_transforms_optimization", + "swc_ecma_usage_analyzer", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_eq_ignore_macros", + "swc_fast_graph", + "swc_graph_analyzer", + "swc_macros_common", + "swc_parallel", + "swc_timer", + "swc_visit", "tracing", ] @@ -5042,7 +5123,7 @@ dependencies = [ "bit_field", "half", "lebe", - "miniz_oxide 0.8.8", + "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", @@ -5104,7 +5185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", - "rustc_version", + "rustc_version 0.4.1", ] [[package]] @@ -5147,6 +5228,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.1" @@ -5154,7 +5241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "miniz_oxide 0.8.8", + "miniz_oxide", ] [[package]] @@ -5311,6 +5398,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "from_variant" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" +dependencies = [ + "proc-macro2", + "swc_macros_common", + "syn 2.0.100", +] + [[package]] name = "fs-err" version = "3.0.0" @@ -5770,9 +5868,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" @@ -6518,6 +6616,15 @@ dependencies = [ "ahash 0.7.8", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -6671,6 +6778,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0" +dependencies = [ + "hashbrown 0.14.5", + "new_debug_unreachable", + "once_cell", + "phf 0.11.3", + "rustc-hash 1.1.0", + "triomphe", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -7143,6 +7264,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "ignore" version = "0.4.23" @@ -7416,6 +7543,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -7764,14 +7903,14 @@ dependencies = [ [[package]] name = "krates" -version = "0.17.5" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5bdd9794c39f6eb77da784fdcd065cc730a95fd0ca7d88ec945ed26c3c5109" +checksum = "c0e4748d9ac5a5c1e557d716a86d1a121caa7dee44c8a3848932c0bdebff075e" dependencies = [ "camino", - "cfg-expr 0.17.2", - "petgraph", - "semver", + "cfg-expr 0.19.0", + "petgraph 0.7.1", + "semver 1.0.26", "serde", "serde_json", ] @@ -8200,6 +8339,15 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "lru" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "lru" version = "0.12.5" @@ -8539,15 +8687,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.8" @@ -8847,6 +8986,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "normpath" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a9da8c9922c35a1033d76f7272dfc2e7ee20392083d75aeea6ced23c6266578" +dependencies = [ + "winapi", +] + [[package]] name = "normpath" version = "1.3.0" @@ -8925,6 +9073,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "serde", ] [[package]] @@ -9819,6 +9968,12 @@ dependencies = [ "path-dedot", ] +[[package]] +name = "path-clean" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" + [[package]] name = "path-dedot" version = "3.1.1" @@ -9944,7 +10099,17 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap 2.9.0", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", "indexmap 2.9.0", ] @@ -10292,7 +10457,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.8", + "miniz_oxide", ] [[package]] @@ -10546,13 +10711,31 @@ dependencies = [ "prost", ] +[[package]] +name = "psm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" +dependencies = [ + "cc", +] + [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ - "ptr_meta_derive", + "ptr_meta_derive 0.1.4", +] + +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive 0.3.0", ] [[package]] @@ -10566,6 +10749,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "qoi" version = "0.4.1" @@ -10684,6 +10878,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_fmt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" + [[package]] name = "rand" version = "0.7.3" @@ -11045,6 +11245,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "remove_dir_all" version = "0.8.4" @@ -11055,7 +11261,7 @@ dependencies = [ "cvt", "fs_at", "libc", - "normpath", + "normpath 1.3.0", "windows-sys 0.59.0", ] @@ -11273,7 +11479,7 @@ dependencies = [ "bytecheck", "bytes", "hashbrown 0.12.3", - "ptr_meta", + "ptr_meta 0.1.4", "rend", "rkyv_derive", "seahash", @@ -11382,13 +11588,22 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.26", ] [[package]] @@ -11615,6 +11830,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + [[package]] name = "same-file" version = "1.0.6" @@ -11801,6 +12022,15 @@ dependencies = [ "to_shmem_derive", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.26" @@ -11810,6 +12040,12 @@ dependencies = [ "serde", ] +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "send_wrapper" version = "0.6.0" @@ -12049,7 +12285,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.1", "syn 2.0.100", "xxhash-rust", ] @@ -12439,6 +12675,25 @@ dependencies = [ "system-deps", ] +[[package]] +name = "sourcemap" +version = "9.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c4ea7042fd1a155ad95335b5d505ab00d5124ea0332a06c8390d200bb1a76a" +dependencies = [ + "base64-simd", + "bitvec", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash 1.1.0", + "rustc_version 0.2.3", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + [[package]] name = "spake2" version = "0.4.0" @@ -12504,7 +12759,7 @@ dependencies = [ "bstr", "bytes", "chrono", - "crc", + "crc 3.2.1", "crossbeam-queue", "either", "event-listener 5.4.0", @@ -12591,7 +12846,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc", + "crc 3.2.1", "digest", "dotenvy", "either", @@ -12638,7 +12893,7 @@ dependencies = [ "bitflags 2.9.0", "byteorder", "chrono", - "crc", + "crc 3.2.1", "dotenvy", "etcetera", "futures-channel", @@ -12705,6 +12960,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "static-self" version = "0.1.2" @@ -12767,6 +13035,18 @@ dependencies = [ "quote", ] +[[package]] +name = "string_enum" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.100", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -13089,36 +13369,476 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.109" +name = "swc_allocator" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "bumpalo", + "hashbrown 0.14.5", + "ptr_meta 0.3.0", + "rustc-hash 1.1.0", + "triomphe", ] [[package]] -name = "syn" -version = "2.0.100" +name = "swc_atoms" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "a640bf2e4430a149c87b5eaf377477ce8615ca7cb808054dd20e79e42da5d6ba" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "hstr", + "once_cell", + "rustc-hash 1.1.0", + "serde", ] [[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.2" +name = "swc_bundler" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88a91910cd8430f88f8987019cf3a96d92a5d5dded3e0ba8203e0379e4a2f6f" +dependencies = [ + "anyhow", + "crc 2.1.0", + "indexmap 2.9.0", + "is-macro", + "once_cell", + "parking_lot", + "petgraph 0.6.5", + "radix_fmt", + "relative-path", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_loader", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_optimization", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_fast_graph", + "swc_graph_analyzer", + "tracing", +] + +[[package]] +name = "swc_cached" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b6a5ef4cfec51d3fa30b73600f206453a37fc30cf1141e4644a57b1ed88616" +dependencies = [ + "ahash 0.8.11", + "anyhow", + "dashmap 5.5.3", + "once_cell", + "regex", + "serde", +] + +[[package]] +name = "swc_common" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22" +dependencies = [ + "ast_node", + "better_scoped_tls", + "cfg-if", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash 1.1.0", + "serde", + "siphasher 0.3.11", + "swc_allocator", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_visit", + "termcolor", + "tracing", + "unicode-width 0.1.14", + "url", +] + +[[package]] +name = "swc_config" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa30931f9b26af8edcb4cce605909d15dcfd7577220b22c50a2988f2a53c4c1" +dependencies = [ + "anyhow", + "indexmap 2.9.0", + "serde", + "serde_json", + "sourcemap", + "swc_cached", + "swc_config_macro", +] + +[[package]] +name = "swc_config_macro" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2ebd37ef52a8555c8c9be78b694d64adcb5e3bc16c928f030d82f1d65fac57" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.100", +] + +[[package]] +name = "swc_ecma_ast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f448db2d1c52ffd2bd3788d89cafd8b5a75b97f0dc8aae00874dda2647f6b6" +dependencies = [ + "bitflags 2.9.0", + "is-macro", + "num-bigint", + "phf 0.11.3", + "scoped-tls", + "serde", + "string_enum", + "swc_atoms", + "swc_common", + "swc_visit", + "unicode-id-start", +] + +[[package]] +name = "swc_ecma_codegen" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f93692de35a77d920ce8d96a46217735e5f86bf42f76cc8f1a60628c347c4c8" +dependencies = [ + "memchr", + "num-bigint", + "once_cell", + "regex", + "serde", + "sourcemap", + "swc_allocator", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen_macros", + "tracing", +] + +[[package]] +name = "swc_ecma_codegen_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9a42f479a6475647e248fa9750982c87cd985e19d1016a1fc18a70682305d1" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.100", +] + +[[package]] +name = "swc_ecma_loader" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a19b132079bfcd19d6fdabce7e55ece93a30787f3b8684c8646ddaf2237812d" +dependencies = [ + "anyhow", + "dashmap 5.5.3", + "lru 0.10.1", + "normpath 0.2.0", + "once_cell", + "parking_lot", + "path-clean", + "pathdiff", + "serde", + "serde_json", + "swc_atoms", + "swc_common", + "tracing", +] + +[[package]] +name = "swc_ecma_minifier" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164291b068cca947462d87ede1baf276f69da137db1a0c66059a8aed81b785b2" +dependencies = [ + "arrayvec", + "indexmap 2.9.0", + "num-bigint", + "num_cpus", + "once_cell", + "parking_lot", + "phf 0.11.3", + "radix_fmt", + "regex", + "rustc-hash 1.1.0", + "ryu-js", + "serde", + "serde_json", + "swc_allocator", + "swc_atoms", + "swc_common", + "swc_config", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_optimization", + "swc_ecma_usage_analyzer", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_parallel", + "swc_timer", + "tracing", +] + +[[package]] +name = "swc_ecma_parser" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92d3a25349d7f612c38d940f09f9c19c7b7aa3bf4d22fbe31ea44fd5354de02" +dependencies = [ + "either", + "new_debug_unreachable", + "num-bigint", + "num-traits", + "phf 0.11.3", + "serde", + "smallvec", + "smartstring", + "stacker", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "tracing", + "typed-arena", +] + +[[package]] +name = "swc_ecma_transforms_base" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fdc36d220bcd51f70b1d78bdd8c1e1a172b4e594c385bdd9614b84a7c0e112" +dependencies = [ + "better_scoped_tls", + "bitflags 2.9.0", + "indexmap 2.9.0", + "once_cell", + "phf 0.11.3", + "rustc-hash 1.1.0", + "serde", + "smallvec", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_parallel", + "tracing", +] + +[[package]] +name = "swc_ecma_transforms_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6845dfb88569f3e8cd05901505916a8ebe98be3922f94769ca49f84e8ccec8f7" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.100", +] + +[[package]] +name = "swc_ecma_transforms_optimization" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4232534b28fc57b745e8c709723544e5548af29abaa62281eab427099f611d" +dependencies = [ + "dashmap 5.5.3", + "indexmap 2.9.0", + "once_cell", + "petgraph 0.6.5", + "rustc-hash 1.1.0", + "serde_json", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_fast_graph", + "tracing", +] + +[[package]] +name = "swc_ecma_usage_analyzer" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15eb86aaa82d7ec4c1a6c3a8a824b1fdbbaace73c3ed81035a1fbbac49f8e0bd" +dependencies = [ + "indexmap 2.9.0", + "rustc-hash 1.1.0", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_timer", + "tracing", +] + +[[package]] +name = "swc_ecma_utils" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c9d22b4883dc6d6c21a8216bbf5aacedd7f104230b1557367ae126a2ec3a2b5" +dependencies = [ + "indexmap 2.9.0", + "num_cpus", + "once_cell", + "rustc-hash 1.1.0", + "ryu-js", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_visit", + "swc_parallel", + "tracing", + "unicode-id", +] + +[[package]] +name = "swc_ecma_visit" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04c06c1805bda18c27165560f1617a57453feb9fb0638d90839053641af42d4" +dependencies = [ + "new_debug_unreachable", + "num-bigint", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_visit", + "tracing", +] + +[[package]] +name = "swc_eq_ignore_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "swc_fast_graph" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22e0a0478b1b06610453a97c8371cafa742e371a79aff860ccfbabe1ab160a7" +dependencies = [ + "indexmap 2.9.0", + "petgraph 0.6.5", + "rustc-hash 1.1.0", + "swc_common", +] + +[[package]] +name = "swc_graph_analyzer" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b9841af596d2ddb37e56defca81387b60a14863e251cede839d1e349e6209d" +dependencies = [ + "auto_impl", + "petgraph 0.6.5", + "swc_common", + "swc_fast_graph", + "tracing", +] + +[[package]] +name = "swc_macros_common" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "swc_parallel" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cde1a0f344924be62d01de0c8a98e840feae271b77dc8c1d9d2e340687225c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "swc_timer" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db06b46cc832f7cf83c2ce21905fc465d01443a2bdccf63644383e1f5847532" +dependencies = [ + "tracing", +] + +[[package]] +name = "swc_visit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3" +dependencies = [ + "either", + "new_debug_unreachable", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ @@ -13354,7 +14074,7 @@ dependencies = [ "plist", "regex", "rpm", - "semver", + "semver 1.0.26", "serde", "serde_json", "sha1", @@ -13428,7 +14148,7 @@ dependencies = [ "memchr", "phf 0.11.3", "regex", - "semver", + "semver 1.0.26", "serde", "serde-untagged", "serde_json", @@ -14111,6 +14831,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "serde", + "stable_deref_trait", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -14207,6 +14937,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typeid" version = "1.0.3" @@ -14349,6 +15085,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" +[[package]] +name = "unicode-id" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" + +[[package]] +name = "unicode-id-start" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -15019,7 +15767,7 @@ dependencies = [ "bitflags 2.9.0", "hashbrown 0.14.5", "indexmap 2.9.0", - "semver", + "semver 1.0.26", "serde", ] @@ -15041,7 +15789,7 @@ dependencies = [ "bitflags 2.9.0", "hashbrown 0.15.2", "indexmap 2.9.0", - "semver", + "semver 1.0.26", "serde", ] @@ -15053,7 +15801,7 @@ checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" dependencies = [ "bitflags 2.9.0", "indexmap 2.9.0", - "semver", + "semver 1.0.26", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d1772239cc..622105eb3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -296,6 +296,7 @@ separator = "0.4.1" pretty_assertions = "1.4.0" serde_repr = "0.1" hyper-util = "0.1.10" +krates = { version = "0.19.0" } # desktop wry = { version = "0.50.3", default-features = false } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 819b714eae..3ef45cd126 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -51,7 +51,7 @@ rayon = { workspace = true } futures-channel = { workspace = true } target-lexicon = { version = "0.13.2", features = ["serde", "serde_support"] } cargo-config2 = { workspace = true, optional = true } -krates = { version = "0.17.5" } +krates = { workspace = true } regex = "1.11.1" console = "0.15.11" ctrlc = "3.4.5" @@ -132,6 +132,7 @@ dircpy = "0.3.19" plist = "1.7.0" memoize = "0.5.1" wasm-encoder = "0.228.0" +backtrace = "0.3.74" [build-dependencies] From 6f3a71b828a24296f9fc5b5b6b1d09215a08c1ae Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 17 Apr 2025 21:44:16 -0700 Subject: [PATCH 153/301] hoist main funcs into the ifunc table, fixing wasm --- examples/fullstack-hello-world/src/main.rs | 14 +- packages/cli/src/build/patch.rs | 289 ++++++++++++++------- 2 files changed, 210 insertions(+), 93 deletions(-) diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 61921fa525..0e328b4234 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -23,26 +23,34 @@ fn app() -> Element { } button { onclick: move |_| async move { - text.set(say_hi().await.unwrap()); - text.set(say_hi().await.unwrap()); + text.set("fuuuuuuuu yasdasdasdes it works".to_string()); }, "Say hi!" } "Server said: {text}" Child2 { i: 123 } + Child3 { i: "gahahsdhasdhahsd" } } } #[component] fn Child2(i: i32) -> Element { + let abc = 123; rsx! { div { "Hello from the child component!" } } } +#[component] +fn Child3(i: String) -> Element { + rsx! { + div { "Hello from the child component {i}!" } + } +} + #[server] async fn say_hi() -> Result { - Ok("DUAL asdasd ACHIEVED?asdasdads????!".to_string()) + Ok("DUAL asdasd ACHIEVEDACHIEVEDACHIEVEDACHIEVEDACHIEVED?asdasdads????!".to_string()) } #[server] diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index c7c718e9bc..892c6b65aa 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -136,6 +136,8 @@ fn create_wasm_jump_table(original: &Path, patch: &Path) -> anyhow::Result anyhow::Result(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap<&'a str, i32> { - let mut name_to_ifunc_index = HashMap::new(); - // name -> index // we want to export *all* these functions - let all_funcs = syms - .iter() - .flat_map(|sym| match sym { - SymbolInfo::Func { index, name, .. } => Some((name.unwrap(), *index)), - _ => None, - }) - .collect::>(); - - let mut ref_funcs = HashMap::new(); + let namemap = fn_name_map(syms); + let mut indexes_to_names = HashMap::>::new(); + for (n, i) in namemap.iter() { + indexes_to_names.entry(*i).or_default().push(*n); + } let mut offsets = HashMap::new(); @@ -186,58 +182,28 @@ fn collect_func_ifuncs<'a>(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap match &el.items { ElementItems::Functions(ids) => { for (idx, id) in ids.iter().enumerate() { - offsets.insert(id.index(), offset + idx as i32); + let f = m.funcs.get(*id); + offsets.insert(f.name.as_deref().unwrap(), offset + idx as i32); } } - ElementItems::Expressions(ref_type, const_exprs) => { - for e in const_exprs { - match e { - walrus::ConstExpr::Value(value) => panic!(), - walrus::ConstExpr::Global(id) => panic!(), - walrus::ConstExpr::RefNull(ref_type) => panic!(), - walrus::ConstExpr::RefFunc(id) => { - let f = m.funcs.get(*id); - ref_funcs.insert(id.index(), f.name.clone()); - tracing::warn!("Ref func name: {:?} {:?}", *id, f.name); - } - } - } - // tracing::info!("Indirect function table is not a function table: {const_exprs:?}"); - // let last = const_exprs.last().clone().unwrap(); - // for (name, i) in make_indirect { - // ids.push(idxs_to_ids[*i as usize]); - // } - } - } - } - - let mut missing = vec![]; - for (name, index) in all_funcs.iter() { - if let Some(offset) = offsets.get(&(*index as _)) { - name_to_ifunc_index.insert(*name, *offset); - } else { - missing.push((name, index)); - if let Some(shim) = ref_funcs.get(&(*index as _)) { - tracing::error!( - "Ref func was transformed! {:?} -> {:?} -> {:?}", - index, - shim, - all_funcs.iter().find(|(k, v)| **v == *index) - ); - } + ElementItems::Expressions(ref_type, const_exprs) => {} } - // let offset = offsets.get(&(index as _)).unwrap(); } - tracing::info!("There are {} missing ifuncs", missing.len()); - tracing::info!("some are: {:#?}", &missing[0..missing.len().min(10)]); - - // name_to_ifunc_index.insert( - // m.funcs.get(*id).name.as_ref().unwrap().as_str(), - // offset + idx as i32, - // ); + offsets +} - name_to_ifunc_index +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +struct WrongFnIndex(u32); +fn fn_name_map<'a>(syms: &[SymbolInfo<'a>]) -> HashMap<&'a str, WrongFnIndex> { + let all_funcs = syms + .iter() + .flat_map(|sym| match sym { + SymbolInfo::Func { index, name, .. } => Some((name.unwrap(), WrongFnIndex(*index))), + _ => None, + }) + .collect::>(); + all_funcs } /// Resolve the undefined symbols in the incrementals against the original binary, returning an object @@ -470,22 +436,28 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { // Due to monomorphizations, functions will get merged and multiple names will point to the same function. // Walrus loses this information, so we need to manually parse the names table to get the indices // and names of these functions. + // + // Unfortunately, the indicies it gives us ARE NOT VALID. + // We need to work around it by using the FunctionId from the module as a link between the merged function names. let raw_data = parse_bytes_to_data_segment(bytes)?; let ifunc_map = collect_func_ifuncs(&module, &raw_data); + let ifuncs = module + .funcs + .iter() + .filter_map(|f| ifunc_map.get(f.name.as_deref()?).map(|_| f.id())) + .collect::>(); let ifunc_table_initialzer = module.elements.iter().last().unwrap().id(); - // name -> index - // we want to export *all* these functions - let all_funcs = raw_data + let all_funcs = fn_name_map(&raw_data); + let wrong_to_right = module + .funcs .iter() - .flat_map(|sym| match sym { - SymbolInfo::Func { index, name, .. } => Some((name.unwrap(), *index)), - _ => None, + .filter_map(|f| { + let name = f.name.as_deref().unwrap(); + Some((all_funcs.get(name)?.clone(), f.id())) }) .collect::>(); - let index_to_func = module.funcs.iter().enumerate().collect::>(); - let mut already_exported = module .exports .iter() @@ -497,36 +469,49 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { ) .collect::>(); - let make_indirect: Vec<_> = all_funcs - .iter() - .filter(|(name, id)| !ifunc_map.contains_key(*name)) - .collect(); + let mut make_indirect = vec![]; - for (&name, &index) in all_funcs.iter() { - let func = index_to_func.get(&(index as usize)).unwrap(); + for (name, wrong_idx) in all_funcs.iter() { + let f = wrong_to_right.get(wrong_idx).unwrap().clone(); + if bindgen_funcs.contains(&f) { + continue; + } + let func = module.funcs.get(f); if let FunctionKind::Local(_local) = &func.kind { - if !already_exported.contains(name) { - module.exports.add(&name, func.id()); + if !already_exported.contains(*name) { + module.exports.add(*name, func.id()); already_exported.insert(name.to_string()); } + + if !ifuncs.contains(&f) { + make_indirect.push(func.id()); + } } } - let idxs_to_ids = module.funcs.iter().map(|f| f.id()).collect::>(); - tracing::info!("Hoisting {} functions", make_indirect.len()); - match &mut module.elements.get_mut(ifunc_table_initialzer).items { + tracing::trace!("Hoisting {} functions", make_indirect.len()); + let seg = module.elements.get_mut(ifunc_table_initialzer); + let make_indirect_count = make_indirect.len() as u64; + match &mut seg.items { ElementItems::Functions(ids) => { - for (name, i) in make_indirect { - ids.push(idxs_to_ids[*i as usize]); + for func in make_indirect { + ids.push(func); } } ElementItems::Expressions(ref_type, const_exprs) => { - panic!("Indirect function table is not a function table: {const_exprs:?}"); - // let last = const_exprs.last().clone().unwrap(); - // for (name, i) in make_indirect { - // ids.push(idxs_to_ids[*i as usize]); - // } + panic!("Indirect function table is not a function table: {const_exprs:?}") } + }; + + let table = match seg.kind { + ElementKind::Active { table, offset } => table, + _ => todo!(), + }; + + let table = module.tables.get_mut(table); + table.initial += make_indirect_count; + if let Some(max) = table.maximum { + table.maximum = Some(max + make_indirect_count); } Ok(module.emit_wasm()) @@ -600,16 +585,9 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result for t in new.imports.iter() { match t.module.as_str() { "GOT.func" => { - // funcs.push((t.id(), t.name.as_str())); - funcs.push(( t.id(), *ifunc_map.get(t.name.as_str()).unwrap_or_else(|| { - // let exists = old - // .funcs - // .iter() - // .find(|f| f.name.as_deref().unwrap_or_default() == t.name) - // .map(|f| f.id()); let exists = old.exports.get_func(t.name.as_str()); panic!("failed to find GOT.func: {} -> {exists:?}", t.name.as_str()) }), @@ -1353,3 +1331,134 @@ fn make_stub_file( ) .unwrap() } + +#[test] +fn parse_wasm_and_print_globals() { + // let bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/libfullstack-hello-world-example-patch-1744937420042.wasm"); + // let module = walrus::Module::from_buffer(bytes).unwrap(); + // let globals = module.globals.iter().collect::>(); + // for g in globals { + // println!("{:?}: {:?}", g.name, g.kind); + // } +} + +const BADNAME: &str = "_ZN4core3ptr68drop_in_place$LT$alloc..boxed..Box$LT$dyn$u20$core..any..Any$GT$$GT$17hcd167959be12f848E"; + +#[test] +fn parse_wasm_and_print_globals2() { + let bytes = &[]; + // let bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/fullstack-hello-world-example_bg.wasm"); + let module = walrus::Module::from_buffer(bytes).unwrap(); + let func = module.funcs.by_name(BADNAME).unwrap(); + + let data = parse_bytes_to_data_segment(bytes).unwrap(); + let ifuncs = collect_func_ifuncs(&module, &data); + + // 55874 + println!("there are {} ifuncs", ifuncs.len()); + + let ifunc = ifuncs.get(BADNAME).unwrap(); + + println!("ifunc entry: {:?}", ifunc); +} + +#[test] +fn hoists() { + let bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); + let out_bytes = prepare_wasm_base_module(bytes).unwrap(); + + let out = walrus::Module::from_buffer(&out_bytes).unwrap(); + let syms = parse_bytes_to_data_segment(&out_bytes).unwrap(); + let ifuncs = collect_func_ifuncs(&out, &syms); + + // 57001 + println!("there are {} ifuncs", ifuncs.len()); + + let ifunc = ifuncs.get(BADNAME).unwrap(); + println!("ifunc entry: {:?}", ifunc); +} + +#[test] +fn delta() { + let pre_bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); + // let prepared_out_bytes = prepare_wasm_base_module(pre_bytes).unwrap(); + + let prepared_out_bytes = pre_bytes; + + let pre_module = walrus::Module::from_buffer(prepared_out_bytes).unwrap(); + // let pre_module = walrus::Module::from_buffer(&prepared_out_bytes).unwrap(); + let pre_syms = parse_bytes_to_data_segment(prepared_out_bytes).unwrap(); + let pre_ifuncs = collect_func_ifuncs(&pre_module, &pre_syms); + let pre_name_map = fn_name_map(&pre_syms); + + // let bg_bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/fullstack-hello-world-example_bg.wasm"); + let bg_bytes = &[]; + let bg_module = walrus::Module::from_buffer(bg_bytes).unwrap(); + let bg_syms = parse_bytes_to_data_segment(bg_bytes).unwrap(); + let bg_ifuncs = collect_func_ifuncs(&bg_module, &bg_syms); + let bg_name_map = fn_name_map(&bg_syms); + + let pre_funcs = pre_module + .funcs + .iter() + .map(|f| (f.id().index(), f)) + .collect::>(); + + let bg_funcs = bg_module + .funcs + .iter() + .map(|f| (f.id().index(), f)) + .collect::>(); + + // for p in pre_ifuncs.iter() { + // if !bg_ifuncs.contains_key(*p.0) { + // println!("pre->: {:?}", p); + + // // let f = pre_funcs[&(*p.1 as usize)]; + // // println!("pre func: {:?}", f.name); + // } + // } + + let mut bad = 0; + for p in pre_name_map.iter() { + // if !bg_name_map.contains_key(*p.0) { + // println!("pre->: {:?}", p); + // } + + let pre = pre_funcs.get(&(p.1 .0 as usize)).unwrap(); + if pre.name.as_deref() != Some(*p.0) { + // println!("pre->: {:?} -> {:?}", pre.name, p.0); + bad += 1; + } + } + println!("bad: {bad}"); + println!("total: {}", bg_name_map.len()); + + let mut bad = 0; + for p in bg_name_map.iter() { + // if !bg_name_map.contains_key(*p.0) { + // println!("pre->: {:?}", p); + // } + + let bg = bg_funcs.get(&(p.1 .0 as usize)).unwrap(); + if bg.name.as_deref() != Some(*p.0) { + // println!("pre->: {:?} -> {:?}", pre.name, p.0); + bad += 1; + } + } + + println!("bad: {bad}"); + println!("total: {}", bg_name_map.len()); + + // for p in bg_ifuncs { + // if !pre_ifuncs.contains_key(p.0) { + // println!("bg->: {:?}", p); + // } + // } + + // // 57001 + // println!("there are {} ifuncs", ifuncs.len()); + + // let ifunc = ifuncs.get(BADNAME).unwrap(); + // println!("ifunc entry: {:?}", ifunc); +} From 8cde9f2cd01efcc450d903620177ec37e38bc58d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 18 Apr 2025 12:29:05 -0700 Subject: [PATCH 154/301] fix tui output for long lines --- packages/cli/src/serve/output.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 801adf1706..9223a90806 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -853,8 +853,8 @@ impl Output { .iter() .map(|line| { // Very important to strip ansi codes before counting graphemes - the ansi codes count as multiple graphemes! - let grapheme_count = console::strip_ansi_codes(line).graphemes(true).count() as u16; - grapheme_count.max(1).div_ceil(term_size.width) + let grapheme_count = console::strip_ansi_codes(line).graphemes(true).count(); + grapheme_count.max(1).div_ceil(term_size.width as usize) as u16 }) .sum::(); @@ -989,7 +989,11 @@ impl Output { // Create the ansi -> raw string line with a width of either the viewport width or the max width let line_length = line.styled_graphemes(Style::default()).count(); - lines.push(AnsiStringLine::new(line_length as _).render(&line)); + if line_length < u16::MAX as usize { + lines.push(AnsiStringLine::new(line_length as _).render(&line)); + } else { + lines.push(line.to_string()) + } } } From ea1f077227e3d8455ac426a25645316fce4bbf0d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 18 Apr 2025 12:30:11 -0700 Subject: [PATCH 155/301] add more linker logging --- examples/fullstack-hello-world/src/main.rs | 131 +++++++---- packages/cli/src/build/patch.rs | 246 +++++++++++++++++---- packages/cli/src/build/request.rs | 17 ++ packages/cli/src/cli/link.rs | 9 +- 4 files changed, 317 insertions(+), 86 deletions(-) diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 0e328b4234..70dd941a46 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -11,59 +11,98 @@ fn main() { } fn app() -> Element { - let mut text = use_signal(|| "...".to_string()); - + // let mut t = use_signal(|| 0); rsx! { h1 { "Hot patch serverfns!" } - button { - onclick: move |_| async move { - text.set(say_hi().await.unwrap()); - }, - "Say hi!" - } - button { - onclick: move |_| async move { - text.set("fuuuuuuuu yasdasdasdes it works".to_string()); - }, - "Say hi!" - } - "Server said: {text}" - Child2 { i: 123 } - Child3 { i: "gahahsdhasdhahsd" } + // button { + // onclick: move |_| { + // t += 1; + // }, + // "Say hi!" + // } + // "{t}" } } -#[component] -fn Child2(i: i32) -> Element { - let abc = 123; - rsx! { - div { "Hello from the child component!" } - } -} +// fn app() -> Element { +// let mut text = use_signal(|| "...".to_string()); -#[component] -fn Child3(i: String) -> Element { - rsx! { - div { "Hello from the child component {i}!" } - } -} +// rsx! { +// h1 { "Hot patch serverfns!" } +// button { +// onclick: move |_| async move { +// text.set(say_hi().await.unwrap()); +// }, +// "Say hi!" +// } +// button { +// onclick: move |_| async move { +// text.set("wooo!!!".to_string()); +// }, +// "Say hi!!!!" +// } +// button { +// onclick: move |_| async move { +// text.set("wooo2!!!".to_string()); +// }, +// "Say hi!" +// } +// button { +// onclick: move |_| async move { +// text.set("wooo3!!!".to_string()); +// }, +// "Say hi!" +// } +// "Server said: {text}" +// Child1 { i: 123 } +// Child3 { i: "one" } +// // Child3 { i: "two" } +// // Child3 { i: "three" } +// // Child3 { i: "four" } +// // Child3 { i: "five" } +// // Child3 { i: "six" } +// // Child3 { i: "seven" } +// } +// } -#[server] -async fn say_hi() -> Result { - Ok("DUAL asdasd ACHIEVEDACHIEVEDACHIEVEDACHIEVEDACHIEVED?asdasdads????!".to_string()) -} +// #[component] +// fn Child1(i: i32) -> Element { +// let abc = 123; +// rsx! { +// div { "Hello from the child component!" } +// } +// } -#[server] -async fn say_bye() -> Result { - Ok("goodbye!".to_string()) -} +// #[component] +// fn Child3(i: String) -> Element { +// let mut state = use_signal(|| 0); +// rsx! { +// div { +// h3 { "Hello from the child component {i} -> {state}!" } +// button { +// onclick: move |_| state += 1, +// "Increment" +// } +// } +// } +// } -#[server] -async fn say_bye2() -> Result { - Ok("goodbye1!".to_string()) -} +// #[server] +// async fn say_hi() -> Result { +// Ok("DUAL achieved!".to_string()) +// } -#[server] -async fn say_bye3() -> Result { - Ok("goodbye2!".to_string()) -} +// #[server] +// async fn say_bye() -> Result { +// Ok("goodbye!".to_string()) +// } + +// #[server] +// async fn say_bye2() -> Result { +// Ok("goodbye1!".to_string()) +// } + +// #[server] +// async fn say_bye3() -> Result { +// Ok("goodbye2!".to_string()) +// } diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 892c6b65aa..7fa7210e90 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -12,7 +12,15 @@ use object::{ RelocationFlags, RelocationTarget, SectionIndex, SectionKind, SymbolFlags, SymbolKind, SymbolScope, }; -use std::{cmp::Ordering, ffi::OsStr, fs, ops::Deref, panic, path::PathBuf}; +use std::{ + cmp::Ordering, + ffi::OsStr, + fs, + ops::{Deref, Range}, + panic, + path::PathBuf, + sync::{Arc, RwLock}, +}; use std::{ collections::{BTreeMap, HashMap, HashSet}, path::Path, @@ -126,8 +134,8 @@ fn create_wasm_jump_table(original: &Path, patch: &Path) -> anyhow::Result Result> { - let mut module = walrus::Module::from_buffer(bytes)?; + let (mut module, ids, fns_to_ids) = parse_module_with_ids(bytes)?; + // let mut module = walrus::Module::from_buffer(bytes)?; let bindgen_funcs = collect_all_wasm_bindgen_funcs(&module); @@ -440,7 +449,7 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { // Unfortunately, the indicies it gives us ARE NOT VALID. // We need to work around it by using the FunctionId from the module as a link between the merged function names. let raw_data = parse_bytes_to_data_segment(bytes)?; - let ifunc_map = collect_func_ifuncs(&module, &raw_data); + let ifunc_map = collect_func_ifuncs(&module, &raw_data.symbols); let ifuncs = module .funcs .iter() @@ -448,15 +457,15 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { .collect::>(); let ifunc_table_initialzer = module.elements.iter().last().unwrap().id(); - let all_funcs = fn_name_map(&raw_data); - let wrong_to_right = module - .funcs - .iter() - .filter_map(|f| { - let name = f.name.as_deref().unwrap(); - Some((all_funcs.get(name)?.clone(), f.id())) - }) - .collect::>(); + // let all_funcs = fn_name_map(&raw_data.symbols); + // let wrong_to_right = module + // .funcs + // .iter() + // .filter_map(|f| { + // let name = f.name.as_deref().unwrap(); + // Some((all_funcs.get(name)?.clone(), f.id())) + // }) + // .collect::>(); let mut already_exported = module .exports @@ -471,8 +480,19 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { let mut make_indirect = vec![]; - for (name, wrong_idx) in all_funcs.iter() { - let f = wrong_to_right.get(wrong_idx).unwrap().clone(); + // for (name, wrong_idx) in all_funcs.iter() { + + // Not all monos make it in! We are relying on this with our event converter bs + // + // https://github.com/rust-lang/rust/blob/master/compiler/rustc_monomorphize/src/collector.rs + // + // + for (name, index) in raw_data.code_symbol_map.iter() { + if name.contains("ZN11dioxus_html6events137_$LT$impl$u20$core..convert..From$LT$$RF$dioxus_html..events..PlatformEventData$GT$$u20$for$u20$dioxus_html..events..mouse..MouseData$GT$4from17heffc5924f07140a2E") { + panic!("Found a core::any::Any symbol: {name} in {index:?}"); + } + + let f = ids[*index as usize]; if bindgen_funcs.contains(&f) { continue; } @@ -575,7 +595,7 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result let mut new: walrus::Module = walrus::Module::from_buffer(new_bytes)?; let raw_data = parse_bytes_to_data_segment(&old_bytes)?; - let ifunc_map = collect_func_ifuncs(&old, &raw_data); + let ifunc_map = collect_func_ifuncs(&old, &raw_data.symbols); let global_map = collect_global_map(&old); let mut mems = vec![]; @@ -600,15 +620,12 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result // Satisfies the GOT.func imports. They exist as regular imports, but we need to make the indirect call for (imp_id, val) in funcs { - // for (import_id, name) in funcs { let imp = new.imports.get(imp_id); let global_id = match imp.kind { ImportKind::Global(id) => id, _ => todo!(), }; - // new.globals.get_mut(global_id).kind = - // walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32(val as i32))); - // new.imports.delete(imp_id); + tracing::info!("Importing GOT.func: {} -> real: {:?}", imp.name, val); new.globals.get_mut(global_id).kind = walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32(val as i32))); new.imports.delete(imp_id); @@ -618,20 +635,46 @@ pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result // remove the "GOT.data.internal" name for mem in mems { let imp = new.imports.get(mem); + let data_symbol_idx = *raw_data.data_symbol_map.get(imp.name.as_str()).unwrap(); + let data_symbol = raw_data.data_symbols.get(&data_symbol_idx).unwrap(); let name = format!("GOT.data.internal.{}", imp.name); - let val = global_map.get(name.as_str()).unwrap_or_else(|| { - let non_got = global_map.get(name.as_str()); - panic!( - "failed to find GOT.mem: {} -> non got: {non_got:?}", - name.as_str() - ) - }); + let val_from_got = global_map.get(name.as_str()); + // let val_from_got = global_map.get(name.as_str()).unwrap_or_else(|| { + // let non_got = global_map.get(name.as_str()); + // panic!( + // "failed to find GOT.mem: {} -> non got: {non_got:?}", + // name.as_str() + // ) + // }); + // let offset = data_symbol.segment_offset as i32; + let data = old.data.iter().nth(data_symbol.which_data_segment).unwrap(); + let offset = match data.kind { + walrus::DataKind::Active { offset, .. } => match offset { + walrus::ConstExpr::Value(walrus::ir::Value::I32(idx)) => idx, + walrus::ConstExpr::Value(walrus::ir::Value::I64(idx)) => idx as i32, + _ => panic!(), + }, + _ => todo!(), + }; + let global_id = match imp.kind { ImportKind::Global(id) => id, _ => todo!(), }; - new.globals.get_mut(global_id).kind = - walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32(*val))); + let data_offset = offset + data_symbol.segment_offset as i32; + + tracing::info!( + "GOT.mem: {} -> real: {:?} seg_offset: {} data_offset: {}\n({:?})", + name, + val_from_got, + data_symbol.segment_offset, + data_offset, + data_symbol + ); + + new.globals.get_mut(global_id).kind = walrus::GlobalKind::Local(walrus::ConstExpr::Value( + walrus::ir::Value::I32(data_offset), + )); new.imports.delete(mem); } @@ -660,14 +703,20 @@ fn collect_global_map(old: &Module) -> HashMap<&str, i32> { /// We need to do this for data symbols because walrus doesn't provide the right range and offset /// information for data segments. Fortunately, it provides it for code sections, so we only need to /// do a small amount extra of parsing here. -fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result> { +fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result { let parser = wasmparser::Parser::new(0); let mut parser = parser.parse_all(bytes); + let mut segments = vec![]; + let mut data_range = 0..0; let mut symbols = vec![]; // Process the payloads in the raw wasm file so we can extract the specific sections we need while let Some(Ok(payload)) = parser.next() { match payload { + Payload::DataSection(section) => { + data_range = section.range(); + segments = section.into_iter().collect::, _>>()? + } Payload::CustomSection(section) if section.name() == "linking" => { let reader = BinaryReader::new(section.data(), 0); let reader = LinkingSectionReader::new(reader)?; @@ -681,7 +730,79 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result> { } } - Ok(symbols) + // Accumulate the data symbols into a btreemap for later use + let mut data_symbols = BTreeMap::new(); + let mut data_symbol_map = HashMap::new(); + let mut code_symbol_map = BTreeMap::new(); + for (index, symbol) in symbols.iter().enumerate() { + let name = match symbol { + SymbolInfo::Func { flags, index, name } => *name, + SymbolInfo::Data { + flags, + name, + symbol, + } => Some(*name), + SymbolInfo::Global { flags, index, name } => *name, + SymbolInfo::Section { flags, section } => None, + SymbolInfo::Event { flags, index, name } => *name, + SymbolInfo::Table { flags, index, name } => *name, + }; + + if let Some(name) = name { + // ZN59_$LT$dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt17h8ab3728dab9d43e8 + // if name.contains("core..any..Any$u20$as$u20$core..fmt..Debug$GT") { + // panic!("Found a core::any::Any symbol: {name} in {symbol:?}"); + // } + } + + if let SymbolInfo::Func { name, index, .. } = symbol { + if let Some(name) = name { + code_symbol_map.insert(*name, *index as usize); + } + continue; + } + + let SymbolInfo::Data { + symbol: Some(symbol), + name, + .. + } = symbol + else { + continue; + }; + + data_symbol_map.insert(*name, index); + + if symbol.size == 0 { + continue; + } + + let data_segment = segments + .get(symbol.index as usize) + .context("Failed to find data segment")?; + let offset: usize = + data_segment.range.end - data_segment.data.len() + (symbol.offset as usize); + let range = offset..(offset + symbol.size as usize); + + data_symbols.insert( + index, + DataSymbol { + index, + range, + segment_offset: symbol.offset as usize, + symbol_size: symbol.size as usize, + which_data_segment: symbol.index as usize, + }, + ); + } + + Ok(RawDataSection { + data_range, + symbols, + data_symbols, + data_symbol_map, + code_symbol_map, + }) } struct SymbolMap<'a> { @@ -1352,7 +1473,7 @@ fn parse_wasm_and_print_globals2() { let func = module.funcs.by_name(BADNAME).unwrap(); let data = parse_bytes_to_data_segment(bytes).unwrap(); - let ifuncs = collect_func_ifuncs(&module, &data); + let ifuncs = collect_func_ifuncs(&module, &data.symbols); // 55874 println!("there are {} ifuncs", ifuncs.len()); @@ -1369,7 +1490,7 @@ fn hoists() { let out = walrus::Module::from_buffer(&out_bytes).unwrap(); let syms = parse_bytes_to_data_segment(&out_bytes).unwrap(); - let ifuncs = collect_func_ifuncs(&out, &syms); + let ifuncs = collect_func_ifuncs(&out, &syms.symbols); // 57001 println!("there are {} ifuncs", ifuncs.len()); @@ -1388,15 +1509,15 @@ fn delta() { let pre_module = walrus::Module::from_buffer(prepared_out_bytes).unwrap(); // let pre_module = walrus::Module::from_buffer(&prepared_out_bytes).unwrap(); let pre_syms = parse_bytes_to_data_segment(prepared_out_bytes).unwrap(); - let pre_ifuncs = collect_func_ifuncs(&pre_module, &pre_syms); - let pre_name_map = fn_name_map(&pre_syms); + let pre_ifuncs = collect_func_ifuncs(&pre_module, &pre_syms.symbols); + let pre_name_map = fn_name_map(&pre_syms.symbols); // let bg_bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/fullstack-hello-world-example_bg.wasm"); let bg_bytes = &[]; let bg_module = walrus::Module::from_buffer(bg_bytes).unwrap(); let bg_syms = parse_bytes_to_data_segment(bg_bytes).unwrap(); - let bg_ifuncs = collect_func_ifuncs(&bg_module, &bg_syms); - let bg_name_map = fn_name_map(&bg_syms); + let bg_ifuncs = collect_func_ifuncs(&bg_module, &bg_syms.symbols); + let bg_name_map = fn_name_map(&bg_syms.symbols); let pre_funcs = pre_module .funcs @@ -1462,3 +1583,52 @@ fn delta() { // let ifunc = ifuncs.get(BADNAME).unwrap(); // println!("ifunc entry: {:?}", ifunc); } + +struct RawDataSection<'a> { + data_range: Range, + symbols: Vec>, + code_symbol_map: BTreeMap<&'a str, usize>, + data_symbols: BTreeMap, + data_symbol_map: HashMap<&'a str, usize>, +} + +#[derive(Debug)] +struct DataSymbol { + index: usize, + range: Range, + segment_offset: usize, + symbol_size: usize, + which_data_segment: usize, +} + +/// Parse a module and return the mapping of index to FunctionID. +/// We'll use this mapping to remap ModuleIDs +fn parse_module_with_ids( + bindgened: &[u8], +) -> Result<(Module, Vec, HashMap)> { + let ids = Arc::new(RwLock::new(Vec::new())); + let ids_ = ids.clone(); + let module = Module::from_buffer_with_config( + bindgened, + ModuleConfig::new().on_parse(move |_m, our_ids| { + let mut ids = ids_.write().expect("No shared writers"); + let mut idx = 0; + while let Ok(entry) = our_ids.get_func(idx) { + ids.push(entry); + idx += 1; + } + + Ok(()) + }), + )?; + let mut ids_ = ids.write().expect("No shared writers"); + let mut ids = vec![]; + std::mem::swap(&mut ids, &mut *ids_); + + let mut fns_to_ids = HashMap::new(); + for (idx, id) in ids.iter().enumerate() { + fns_to_ids.insert(*id, idx); + } + + Ok((module, ids, fns_to_ids)) +} diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 706ff57abf..677502198e 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -875,6 +875,13 @@ 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); + } + } + Ok(BuildArtifacts { platform, exe, @@ -1301,6 +1308,11 @@ impl BuildRequest { } } + if !res.stdout.is_empty() { + let out = String::from_utf8_lossy(&res.stdout); + tracing::debug!("Output from thin linking: {}", out.trim()); + } + // For some really weird reason that I think is because of dlopen caching, future loads of the // jump library will fail if we don't remove the original fat file. I think this could be // because of library versioning and namespaces, but really unsure. @@ -1347,6 +1359,8 @@ impl BuildRequest { // We turn on both --pie and --experimental-pic but I think we only need --pie. OperatingSystem::Unknown if self.platform == Platform::Web => { out_args.extend([ + "--fatal-warnings".to_string(), + "--verbose".to_string(), "--import-memory".to_string(), "--import-table".to_string(), "--growable-table".to_string(), @@ -1493,6 +1507,9 @@ impl BuildRequest { .unwrap() .display() )); + + tracing::debug!("Running rustc command: {:#?}", rustc_args.args); + Ok(cmd) } diff --git a/packages/cli/src/cli/link.rs b/packages/cli/src/cli/link.rs index ec404624df..7288c9ac56 100644 --- a/packages/cli/src/cli/link.rs +++ b/packages/cli/src/cli/link.rs @@ -97,10 +97,15 @@ impl LinkAction { .output() .expect("Failed to run android linker"); - if !res.stderr.is_empty() { + if !res.stderr.is_empty() || !res.stdout.is_empty() { + _ = std::fs::create_dir_all(self.link_err_file.parent().unwrap()); _ = std::fs::write( self.link_err_file, - String::from_utf8_lossy(&res.stderr).as_bytes(), + format!( + "Linker error: {}\n{}", + String::from_utf8_lossy(&res.stdout), + String::from_utf8_lossy(&res.stderr) + ), ) .unwrap(); } From e589560f97fe1a150538db6343f6e320d8291261 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 18 Apr 2025 14:17:34 -0700 Subject: [PATCH 156/301] wow, incredible, we build fat binaries, holy heckkkkk --- Cargo.lock | 10 + Cargo.toml | 1 + example-projects/simple-web/Cargo.toml | 16 ++ example-projects/simple-web/src/main.rs | 25 ++ examples/fullstack-hello-world/src/main.rs | 16 +- packages/cli/Cargo.toml | 1 + packages/cli/src/build/request.rs | 306 ++++++++++++++++++--- packages/cli/src/rustcwrapper.rs | 14 + packages/cli/src/serve/mod.rs | 2 +- packages/cli/src/workspace.rs | 9 + 10 files changed, 351 insertions(+), 49 deletions(-) create mode 100644 example-projects/simple-web/Cargo.toml create mode 100644 example-projects/simple-web/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 2ac9430c86..d3a7203db4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3631,6 +3631,7 @@ dependencies = [ "ansi-to-html", "ansi-to-tui", "anyhow", + "ar", "axum 0.8.3", "axum-extra", "axum-server", @@ -12466,6 +12467,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74" +[[package]] +name = "simple-web-example" +version = "0.1.0" +dependencies = [ + "dioxus", + "reqwest 0.12.15", + "serde", +] + [[package]] name = "simplecss" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 622105eb3b..5fa706141b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ members = [ "example-projects/ecommerce-site", "example-projects/wifi-scanner", "example-projects/file-explorer", + "example-projects/simple-web", # Simple examples that require a crate "examples/tailwind", diff --git a/example-projects/simple-web/Cargo.toml b/example-projects/simple-web/Cargo.toml new file mode 100644 index 0000000000..0241145df7 --- /dev/null +++ b/example-projects/simple-web/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "simple-web-example" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus = { workspace = true } +serde = { workspace = true } +reqwest = { workspace = true } + +[features] +default = ["dioxus/web"] +web = ["dioxus/web"] diff --git a/example-projects/simple-web/src/main.rs b/example-projects/simple-web/src/main.rs new file mode 100644 index 0000000000..c4c900afe0 --- /dev/null +++ b/example-projects/simple-web/src/main.rs @@ -0,0 +1,25 @@ +//! Run with: +//! +//! ```sh +//! dx serve --platform web +//! ``` + +use dioxus::prelude::*; + +fn main() { + dioxus::launch(app); +} + +fn app() -> Element { + let mut t = use_signal(|| 0); + rsx! { + h1 { "Hot patch serverfns!" } + button { + onclick: move |_| { + t += 1; + }, + "Say hi!" + } + "{t}" + } +} diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 70dd941a46..6b59234fb8 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -11,16 +11,16 @@ fn main() { } fn app() -> Element { - // let mut t = use_signal(|| 0); + let mut t = use_signal(|| 0); rsx! { h1 { "Hot patch serverfns!" } - // button { - // onclick: move |_| { - // t += 1; - // }, - // "Say hi!" - // } - // "{t}" + button { + onclick: move |_| { + t += 1; + }, + "Say hi!" + } + "{t}" } } diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 3ef45cd126..9ca2191125 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -133,6 +133,7 @@ plist = "1.7.0" memoize = "0.5.1" wasm-encoder = "0.228.0" backtrace = "0.3.74" +ar = "0.9.0" [build-dependencies] diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 677502198e..51c3f54f04 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -15,6 +15,9 @@ //! "normal" builds just use [`BuildMode::Base`], but we also support [`BuildMode::Fat`] and //! [`BuildMode::Thin`]. These builds are used together to power the hot-patching and fast-linking //! engine. +//! - BuildMode::Base: A normal build generated using `cargo rustc` +//! - BuildMode::Fat: A "fat" build where all dependency rlibs are merged into a static library +//! - BuildMode::Thin: A "thin" build that dynamically links against the artifacts produced by the "fat" build //! //! The BuildRequest is also responsible for writing the final build artifacts to disk. This includes //! @@ -330,7 +333,7 @@ use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, future::Future, - io::Write, + io::{Read, Write}, path::{Path, PathBuf}, pin::Pin, process::Stdio, @@ -710,6 +713,10 @@ impl BuildRequest { // Run the cargo build to produce our artifacts let mut artifacts = self.cargo_build(&ctx).await?; + if matches!(ctx.mode, BuildMode::Fat) { + self.perform_fat_link(ctx, &artifacts).await?; + } + // Write the build artifacts to the bundle on the disk match ctx.mode { BuildMode::Thin { aslr_reference, .. } => { @@ -1117,39 +1124,6 @@ impl BuildRequest { Ok(()) } - /// Patches are stored in the same directory as the main executable, but with a name based on the - /// time the patch started compiling. - /// - /// - lib{name}-patch-{time}.(so/dll/dylib) (next to the main exe) - /// - /// Note that weirdly enough, the name of dylibs can actually matter. In some environments, libs - /// can override each other with symbol interposition. - /// - /// Also, on Android - and some Linux, we *need* to start the lib name with `lib` for the dynamic - /// loader to consider it a shared library. - /// - /// todo: the time format might actually be problematic if two platforms share the same build folder. - pub(crate) fn patch_exe(&self, time_start: SystemTime) -> PathBuf { - let path = self.main_exe().with_file_name(format!( - "lib{}-patch-{}", - self.executable_name(), - time_start.duration_since(UNIX_EPOCH).unwrap().as_millis(), - )); - - 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", - _ => "", - }; - - path.with_extension(extension) - } - /// Run our custom linker setup to generate a patch file in the right location /// /// This should be the only case where the cargo output is a "dummy" file and requires us to @@ -1471,6 +1445,254 @@ impl BuildRequest { Ok(out_args) } + /// Patches are stored in the same directory as the main executable, but with a name based on the + /// time the patch started compiling. + /// + /// - lib{name}-patch-{time}.(so/dll/dylib) (next to the main exe) + /// + /// Note that weirdly enough, the name of dylibs can actually matter. In some environments, libs + /// can override each other with symbol interposition. + /// + /// Also, on Android - and some Linux, we *need* to start the lib name with `lib` for the dynamic + /// loader to consider it a shared library. + /// + /// todo: the time format might actually be problematic if two platforms share the same build folder. + pub(crate) fn patch_exe(&self, time_start: SystemTime) -> PathBuf { + let path = self.main_exe().with_file_name(format!( + "lib{}-patch-{}", + self.executable_name(), + time_start.duration_since(UNIX_EPOCH).unwrap().as_millis(), + )); + + 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", + _ => "", + }; + + path.with_extension(extension) + } + + /// When we link together the fat binary, we need to make sure every `.o` file in *every* rlib + /// is taken into account. This is the same work that the rust compiler does when assembling + /// staticlibs. + /// + /// https://github.com/rust-lang/rust/blob/191df20fcad9331d3a948aa8e8556775ec3fe69d/compiler/rustc_codegen_ssa/src/back/link.rs#L448 + /// + /// Since we're going to be passing these to the linker, we need to make sure and not provide any + /// weird files (like the rmeta) file that rustc generates. + /// + /// We discovered the need for this after running into issues with wasm-ld not being able to + /// handle the rmeta file. + /// + /// https://github.com/llvm/llvm-project/issues/55786 + /// + /// When Rust normally handles this, it uses the +whole-archive directive which adjusts how the rlib + /// is written to disk. + /// + /// Since creating this object file can be a lot of work, we cache it in the target dir by hashing + /// the names of the rlibs in the command and storing it in the target dir. That way, when we run + /// this command again, we can just used the cached object file. + /// + /// In theory, we only need to do this for every crate accessible by the current crate, but that's + /// hard acquire without knowing the exported symbols from each crate. + /// + /// todo: I think we can traverse our immediate dependencies and inspect their symbols, unless they `pub use` a crate + /// todo: we should try and make this faster with memmapping + pub(crate) async fn perform_fat_link( + &self, + ctx: &BuildContext, + artifacts: &BuildArtifacts, + ) -> Result<()> { + 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::>(); + + tracing::debug!("Linking with args: {:?}", args); + + // Filter out the rlib files from the arguments + let rlibs = args + .iter() + .filter(|arg| arg.ends_with(".rlib")) + .map(|arg| PathBuf::from(arg)) + .collect::>(); + + // Acquire a hash from the rlib names + let hash_id = Uuid::new_v5( + &Uuid::NAMESPACE_OID, + &rlibs + .iter() + .map(|p| p.file_name().unwrap().to_string_lossy()) + .collect::() + .as_bytes(), + ); + + // Check if we already have a cached object file + let out_ar_path = artifacts + .exe + .with_file_name(format!("libfatdependencies-{hash_id}.a")); + + let mut compiler_rlibs = vec![]; + + // 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 !out_ar_path.exists() || true { + let mut bytes = vec![]; + let mut out_ar = ar::Builder::new(&mut bytes); + for rlib in &rlibs { + tracing::debug!("Adding rlib {:?} to archive", rlib); + + // Skip compiler rlibs since they're missing bitcode + // + // https://github.com/rust-lang/rust/issues/94232#issuecomment-1048342201 + // + // if the rlib is not in the target directory, we skip it. + // + if !rlib.starts_with(self.workspace_dir()) { + compiler_rlibs.push(rlib.clone()); + tracing::debug!("Skipping rlib {:?} since it's not in the target dir", rlib); + continue; + } + + let rlib_contents = std::fs::read(rlib)?; + let mut reader = ar::Archive::new(std::io::Cursor::new(rlib_contents)); + while let Some(Ok(object_file)) = reader.next_entry() { + let identifier = object_file.header().identifier(); + let maybe_str = std::str::from_utf8(identifier).unwrap(); + + if identifier.ends_with(b".rmeta") { + // tracing::debug!("Skipping rmeta file {:?} from archive", maybe_str); + continue; + } + + if !maybe_str.ends_with(".o") { + tracing::debug!("Weird non-object file {:?} from archive", maybe_str); + } + + // if identifier.contains(b".rustup" as &[u8]) { + // tracing::debug!("Skipping rustup file {:?} from archive", maybe_str); + // continue; + // } + + tracing::trace!("Adding object file {:?} to archive", maybe_str); + + out_ar + .append(&object_file.header().clone(), object_file) + .context("Failed to add object file to archive")?; + } + } + + let bytes = out_ar.into_inner().context("Failed to finalize archive")?; + std::fs::write(&out_ar_path, bytes).context("Failed to write archive")?; + tracing::debug!("Wrote fat archive to {:?}", out_ar_path); + } + + // We're going to replace the first rlib in the args with our fat archive + // 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::>(); + if let Some(first_rlib) = args.iter().position(|arg| arg.ends_with(".rlib")) { + args[first_rlib] = match self.platform { + // On wasm, we need to use the --whole-archive flag + Platform::Web => format!("--whole-archive"), + + // On all other platforms, we need to use -force_load + _ => format!("-Wl,-all_load"), + // _ => format!("-Wl,-force_load={}", out_ar_path.display()), + }; + 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()); + } + } + + // We also need to remove the `-o` flag since we want the linker output to end up in the + // rust exe location, not in the deps dir as it normally would. + if let Some(idx) = args.iter().position(|arg| *arg == "-o") { + args.remove(idx + 1); + args.remove(idx); + } + + // We want to go through wasm-ld directly, so we need to remove the -flavor flag + if self.platform == Platform::Web { + let flavor_idx = args.iter().position(|arg| *arg == "-flavor").unwrap(); + args.remove(flavor_idx + 1); + args.remove(flavor_idx); + } + + // And now we can run the linker with our new args + let cc = match self.platform { + // todo: we're using wasm-ld directly, but I think we can drive it with rust-lld and -flavor wasm + Platform::Web => self.workspace.wasm_ld(), + // Platform::Web => self.workspace.rust_lld(), + + // The android clang linker is *special* and has some android-specific flags that we need + // + // Note that this is *clang*, not `lld`. + Platform::Android => android_tools() + .context("Could not determine android tools")? + .android_cc(&self.triple), + + // 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 + // it requires msvc or gnu downloaded, which is a requirement to use rust anyways. + // + // The default linker might actually be slow though, so we could consider using lld or rust-lld + // since those are shipping by default on linux as of 1.86. Window's linker is the really slow one. + // + // https://blog.rust-lang.org/2024/05/17/enabling-rust-lld-on-linux.html + // + // Note that "cc" is *not* a linker. It's a compiler! The arguments we pass need to be in + // the form of `-Wl,` for them to make it to the linker. This matches how rust does it + // which is confusing. + Platform::MacOS + | Platform::Ios + | Platform::Linux + | Platform::Server + | Platform::Liveview + | Platform::Windows => PathBuf::from("cc"), + }; + + tracing::debug!("Final linker args: {:#?}", args); + + // Run the linker directly! + let res = Command::new(cc) + .args(args.iter().skip(1)) + .arg("-o") + .arg(&artifacts.exe) + .output() + .await?; + + if !res.stderr.is_empty() { + let errs = String::from_utf8_lossy(&res.stderr); + if !res.status.success() { + tracing::error!("Failed to generate fat binary: {}", errs.trim()); + } else { + tracing::debug!("Warnings during fat linking: {}", errs.trim()); + } + } + + if !res.stdout.is_empty() { + let out = String::from_utf8_lossy(&res.stdout); + tracing::debug!("Output from fat linking: {}", out.trim()); + } + + Ok(()) + } + /// Assemble the `cargo rustc` / `rustc` command /// /// When building fat/base binaries, we use `cargo rustc`. @@ -1607,7 +1829,9 @@ impl BuildRequest { } // dx *always* links android and thin builds - if self.custom_linker.is_some() || matches!(ctx.mode, BuildMode::Thin { .. }) { + if self.custom_linker.is_some() + || matches!(ctx.mode, BuildMode::Thin { .. } | BuildMode::Fat) + { cargo_args.push(format!( "-Clinker={}", dunce::canonicalize(std::env::current_exe().unwrap()) @@ -1641,13 +1865,13 @@ impl BuildRequest { match self.triple.operating_system { // macOS/iOS use ld64 but through the `cc` interface. OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) => { - cargo_args.push("-Clink-args=-Wl,-all_load".to_string()); + // cargo_args.push("-Clink-args=-Wl,-all_load".to_string()); } // Linux and Android fit under this umbrella, both with the same clang-like entrypoint // and the gnu-ld interface. OperatingSystem::Linux => { - cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); + // cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); } // If windows -Wl,--whole-archive is required since it follows gnu-ld convention. @@ -1655,7 +1879,7 @@ impl BuildRequest { // // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170 OperatingSystem::Windows => { - cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); + // cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); } // if web, -Wl,--whole-archive is required since it follows gnu-ld convention. @@ -1717,7 +1941,9 @@ impl BuildRequest { }; // If we're either zero-linking or using a custom linker, make `dx` itself do the linking. - if self.custom_linker.is_some() || matches!(ctx.mode, BuildMode::Thin { .. }) { + if self.custom_linker.is_some() + || matches!(ctx.mode, BuildMode::Thin { .. } | BuildMode::Fat) + { env_vars.push(( LinkAction::ENV_VAR_NAME, LinkAction { @@ -2857,7 +3083,7 @@ impl BuildRequest { // Make sure to write some entropy to the main.js file so it gets a new hash // If we don't do this, the main.js file will be cached and never pick up the chunk names - let uuid = uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, glue.as_bytes()); + let uuid = Uuid::new_v5(&Uuid::NAMESPACE_URL, glue.as_bytes()); std::fs::OpenOptions::new() .append(true) .open(self.wasm_bindgen_js_output_file()) diff --git a/packages/cli/src/rustcwrapper.rs b/packages/cli/src/rustcwrapper.rs index 4da0025374..da87f23e09 100644 --- a/packages/cli/src/rustcwrapper.rs +++ b/packages/cli/src/rustcwrapper.rs @@ -32,6 +32,20 @@ pub struct RustcArgs { /// /// https://doc.rust-lang.org/cargo/reference/config.html#buildrustc pub async fn run_rustc() { + // if we happen to be both a rustc wrapper and a linker, we want to run the linker if the arguments seem linker-y + // this is a stupid hack + if std::env::args() + .take(5) + .any(|arg| arg.ends_with(".o") || arg == "-flavor") + { + crate::link::LinkAction::from_env() + .expect("Linker action not found") + .run() + .await + .expect("Failed to run linker"); + return; + } + let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR) .expect("DX_RUSTC not set") .into(); diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 0f3d44d0b8..aa2b47dcb9 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -128,7 +128,7 @@ 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!("Build failed: {:#?}", err); } BuilderUpdate::BuildReady { bundle } => { match bundle.mode { diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index f67f16adc3..6870fdf9f5 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -73,6 +73,15 @@ impl Workspace { Ok(workspace) } + pub fn rust_lld(&self) -> PathBuf { + self.sysroot + .join("lib") + .join("rustlib") + .join(Triple::host().to_string()) + .join("bin") + .join("rust-lld") + } + pub fn wasm_ld(&self) -> PathBuf { self.sysroot .join("lib") From 669a8db90598a3273e75c6ee01f1bdfa21e3b335 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 18 Apr 2025 15:08:41 -0700 Subject: [PATCH 157/301] fix args for frontend/backend --- example-projects/simple-web/src/main.rs | 14 ++- examples/fullstack-hello-world/src/main.rs | 99 +++------------------- packages/cli/src/build/patch.rs | 6 +- packages/cli/src/build/request.rs | 34 +++++--- 4 files changed, 48 insertions(+), 105 deletions(-) diff --git a/example-projects/simple-web/src/main.rs b/example-projects/simple-web/src/main.rs index c4c900afe0..e6b08db0db 100644 --- a/example-projects/simple-web/src/main.rs +++ b/example-projects/simple-web/src/main.rs @@ -12,14 +12,20 @@ fn main() { fn app() -> Element { let mut t = use_signal(|| 0); + + rsx! { + h1 { "Hot patch serverfns!!!!!" } + EvalIt {} + } +} + +fn EvalIt() -> Element { rsx! { - h1 { "Hot patch serverfns!" } button { onclick: move |_| { - t += 1; + _ = dioxus::document::eval("window.document.body.style.backgroundColor = 'green';"); }, - "Say hi!" + "eval!" } - "{t}" } } diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 6b59234fb8..9e27f8e462 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -11,98 +11,23 @@ fn main() { } fn app() -> Element { - let mut t = use_signal(|| 0); + let mut text = use_signal(|| "make a request!".to_string()); + rsx! { h1 { "Hot patch serverfns!" } button { - onclick: move |_| { - t += 1; + onclick: move |_| async move { + text.set(do_server_action().await.unwrap()); }, - "Say hi!" + "Request from the server" + } + div { + "server says: {text}" } - "{t}" } } -// fn app() -> Element { -// let mut text = use_signal(|| "...".to_string()); - -// rsx! { -// h1 { "Hot patch serverfns!" } -// button { -// onclick: move |_| async move { -// text.set(say_hi().await.unwrap()); -// }, -// "Say hi!" -// } -// button { -// onclick: move |_| async move { -// text.set("wooo!!!".to_string()); -// }, -// "Say hi!!!!" -// } -// button { -// onclick: move |_| async move { -// text.set("wooo2!!!".to_string()); -// }, -// "Say hi!" -// } -// button { -// onclick: move |_| async move { -// text.set("wooo3!!!".to_string()); -// }, -// "Say hi!" -// } -// "Server said: {text}" -// Child1 { i: 123 } -// Child3 { i: "one" } -// // Child3 { i: "two" } -// // Child3 { i: "three" } -// // Child3 { i: "four" } -// // Child3 { i: "five" } -// // Child3 { i: "six" } -// // Child3 { i: "seven" } -// } -// } - -// #[component] -// fn Child1(i: i32) -> Element { -// let abc = 123; -// rsx! { -// div { "Hello from the child component!" } -// } -// } - -// #[component] -// fn Child3(i: String) -> Element { -// let mut state = use_signal(|| 0); -// rsx! { -// div { -// h3 { "Hello from the child component {i} -> {state}!" } -// button { -// onclick: move |_| state += 1, -// "Increment" -// } -// } -// } -// } - -// #[server] -// async fn say_hi() -> Result { -// Ok("DUAL achieved!".to_string()) -// } - -// #[server] -// async fn say_bye() -> Result { -// Ok("goodbye!".to_string()) -// } - -// #[server] -// async fn say_bye2() -> Result { -// Ok("goodbye1!".to_string()) -// } - -// #[server] -// async fn say_bye3() -> Result { -// Ok("goodbye2!".to_string()) -// } +#[server] +async fn do_server_action() -> Result { + Ok("hello from the server - hotpatched!!".to_string()) +} diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 7fa7210e90..49ac166545 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -488,9 +488,9 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { // // for (name, index) in raw_data.code_symbol_map.iter() { - if name.contains("ZN11dioxus_html6events137_$LT$impl$u20$core..convert..From$LT$$RF$dioxus_html..events..PlatformEventData$GT$$u20$for$u20$dioxus_html..events..mouse..MouseData$GT$4from17heffc5924f07140a2E") { - panic!("Found a core::any::Any symbol: {name} in {index:?}"); - } + // if name.contains("ZN11dioxus_html6events137_$LT$impl$u20$core..convert..From$LT$$RF$dioxus_html..events..PlatformEventData$GT$$u20$for$u20$dioxus_html..events..mouse..MouseData$GT$4from17heffc5924f07140a2E") { + // panic!("Found a core::any::Any symbol: {name} in {index:?}"); + // } let f = ids[*index as usize]; if bindgen_funcs.contains(&f) { diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 51c3f54f04..5bb1e26b32 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1601,22 +1601,34 @@ impl BuildRequest { // 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::>(); if let Some(first_rlib) = args.iter().position(|arg| arg.ends_with(".rlib")) { - args[first_rlib] = match self.platform { + match self.platform { // On wasm, we need to use the --whole-archive flag - Platform::Web => format!("--whole-archive"), + Platform::Web => { + args[first_rlib] = format!("--whole-archive"); + 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()); + } + } // On all other platforms, we need to use -force_load - _ => format!("-Wl,-all_load"), + // todo: this only supports macos for now // _ => format!("-Wl,-force_load={}", out_ar_path.display()), - }; - 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")); + _ => { + args[first_rlib] = format!("-Wl,-all_load"); + 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 + 3, rlib.display().to_string()); - } + // add back the compiler rlibs + for rlib in compiler_rlibs.iter().rev() { + args.insert(first_rlib + 2, rlib.display().to_string()); + } + } + }; } // We also need to remove the `-o` flag since we want the linker output to end up in the From b363fb7abb3a5f90c82bdb4f35871fda8d98df50 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 18 Apr 2025 15:18:43 -0700 Subject: [PATCH 158/301] small cleanups --- packages/cli/src/build/patch.rs | 22 ---------------------- packages/cli/src/build/request.rs | 23 +++++++---------------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 49ac166545..f956f15d8c 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -438,7 +438,6 @@ pub fn resolve_undefined( /// It also moves all functions and memories to be callable indirectly. pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { let (mut module, ids, fns_to_ids) = parse_module_with_ids(bytes)?; - // let mut module = walrus::Module::from_buffer(bytes)?; let bindgen_funcs = collect_all_wasm_bindgen_funcs(&module); @@ -457,16 +456,6 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { .collect::>(); let ifunc_table_initialzer = module.elements.iter().last().unwrap().id(); - // let all_funcs = fn_name_map(&raw_data.symbols); - // let wrong_to_right = module - // .funcs - // .iter() - // .filter_map(|f| { - // let name = f.name.as_deref().unwrap(); - // Some((all_funcs.get(name)?.clone(), f.id())) - // }) - // .collect::>(); - let mut already_exported = module .exports .iter() @@ -480,18 +469,7 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result> { let mut make_indirect = vec![]; - // for (name, wrong_idx) in all_funcs.iter() { - - // Not all monos make it in! We are relying on this with our event converter bs - // - // https://github.com/rust-lang/rust/blob/master/compiler/rustc_monomorphize/src/collector.rs - // - // for (name, index) in raw_data.code_symbol_map.iter() { - // if name.contains("ZN11dioxus_html6events137_$LT$impl$u20$core..convert..From$LT$$RF$dioxus_html..events..PlatformEventData$GT$$u20$for$u20$dioxus_html..events..mouse..MouseData$GT$4from17heffc5924f07140a2E") { - // panic!("Found a core::any::Any symbol: {name} in {index:?}"); - // } - let f = ids[*index as usize]; if bindgen_funcs.contains(&f) { continue; diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 5bb1e26b32..2985878dfb 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1492,6 +1492,10 @@ impl BuildRequest { /// /// https://github.com/llvm/llvm-project/issues/55786 /// + /// Also, crates might not drag in all their dependent code. The monorphizer won't lift trait-based generics: + /// + /// https://github.com/rust-lang/rust/blob/191df20fcad9331d3a948aa8e8556775ec3fe69d/compiler/rustc_monomorphize/src/collector.rs + /// /// When Rust normally handles this, it uses the +whole-archive directive which adjusts how the rlib /// is written to disk. /// @@ -1555,7 +1559,6 @@ impl BuildRequest { // https://github.com/rust-lang/rust/issues/94232#issuecomment-1048342201 // // if the rlib is not in the target directory, we skip it. - // if !rlib.starts_with(self.workspace_dir()) { compiler_rlibs.push(rlib.clone()); tracing::debug!("Skipping rlib {:?} since it's not in the target dir", rlib); @@ -1565,24 +1568,12 @@ impl BuildRequest { let rlib_contents = std::fs::read(rlib)?; let mut reader = ar::Archive::new(std::io::Cursor::new(rlib_contents)); while let Some(Ok(object_file)) = reader.next_entry() { - let identifier = object_file.header().identifier(); - let maybe_str = std::str::from_utf8(identifier).unwrap(); - - if identifier.ends_with(b".rmeta") { - // tracing::debug!("Skipping rmeta file {:?} from archive", maybe_str); + let name = std::str::from_utf8(object_file.header().identifier()).unwrap(); + if name.ends_with(".rmeta") { continue; } - if !maybe_str.ends_with(".o") { - tracing::debug!("Weird non-object file {:?} from archive", maybe_str); - } - - // if identifier.contains(b".rustup" as &[u8]) { - // tracing::debug!("Skipping rustup file {:?} from archive", maybe_str); - // continue; - // } - - tracing::trace!("Adding object file {:?} to archive", maybe_str); + tracing::trace!("Adding object file {:?} to archive", name); out_ar .append(&object_file.header().clone(), object_file) From 2abbafabff9b87f1d513ea7529334888300a9489 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 11:34:32 -0700 Subject: [PATCH 159/301] properly send along build IDs to clients --- Cargo.lock | 1 + examples/fullstack-auth/Cargo.toml | 2 +- packages/cli-config/src/lib.rs | 6 +- packages/cli/src/build/builder.rs | 14 +-- packages/cli/src/build/mod.rs | 10 ++ packages/cli/src/build/patch.rs | 4 +- packages/cli/src/build/request.rs | 155 ++++++++++++++--------------- packages/cli/src/cli/autoformat.rs | 1 - packages/cli/src/cli/run.rs | 11 +- packages/cli/src/serve/runner.rs | 7 +- packages/desktop/src/launch.rs | 4 +- packages/dioxus/Cargo.toml | 12 ++- 12 files changed, 128 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3a7203db4..a560763fd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3571,6 +3571,7 @@ dependencies = [ "dioxus-signals", "dioxus-ssr", "dioxus-web", + "dioxus_server_macro", "env_logger 0.11.8", "futures-util", "manganis", diff --git a/examples/fullstack-auth/Cargo.toml b/examples/fullstack-auth/Cargo.toml index 472cb8b4d8..f792dce515 100644 --- a/examples/fullstack-auth/Cargo.toml +++ b/examples/fullstack-auth/Cargo.toml @@ -48,7 +48,7 @@ optional = true [features] default = [] server = [ - "dioxus-fullstack/axum", + "dioxus-fullstack/server", "dep:dioxus-cli-config", "dep:axum", "dep:tokio", diff --git a/packages/cli-config/src/lib.rs b/packages/cli-config/src/lib.rs index 2a53b6df6e..7a5d134b08 100644 --- a/packages/cli-config/src/lib.rs +++ b/packages/cli-config/src/lib.rs @@ -69,6 +69,7 @@ pub const APP_TITLE_ENV: &str = "DIOXUS_APP_TITLE"; #[doc(hidden)] pub const OUT_DIR: &str = "DIOXUS_OUT_DIR"; pub const SESSION_CACHE_DIR: &str = "DIOXUS_SESSION_CACHE_DIR"; +pub const BUILD_ID: &str = "DIOXUS_BUILD_ID"; /// Reads an environment variable at runtime in debug mode or at compile time in /// release mode. When bundling in release mode, we will not be running under the @@ -317,6 +318,9 @@ pub fn build_id() -> u64 { #[cfg(not(target_arch = "wasm32"))] { - 1 + std::env::var(BUILD_ID) + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0) } } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 333dd4dd29..cd85c41190 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -21,7 +21,7 @@ use tokio::{ task::JoinHandle, }; -use super::{BuildContext, BuildMode}; +use super::{BuildContext, BuildId, BuildMode}; /// The component of the serve engine that watches ongoing builds and manages their state, open handle, /// and progress. @@ -379,13 +379,14 @@ impl AppBuilder { start_fullstack_on_address: Option, open_browser: bool, always_on_top: bool, + build_id: BuildId, ) -> Result<()> { let krate = &self.build; // Set the env vars that the clients will expect // These need to be stable within a release version (ie 0.6.0) let mut envs = vec![ - // (dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()), + (dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()), ( dioxus_cli_config::DEVSERVER_IP_ENV, devserver_ip.ip().to_string(), @@ -394,10 +395,11 @@ impl AppBuilder { dioxus_cli_config::DEVSERVER_PORT_ENV, devserver_ip.port().to_string(), ), - // ( - // dioxus_cli_config::SESSION_CACHE_DIR, - // self.build.session_cache_dir().display().to_string(), - // ), + ( + dioxus_cli_config::SESSION_CACHE_DIR, + self.build.session_cache_dir().display().to_string(), + ), + (dioxus_cli_config::BUILD_ID, build_id.0.to_string()), // unset the cargo dirs in the event we're running `dx` locally // since the child process will inherit the env vars, we don't want to confuse the downstream process ("CARGO_MANIFEST_DIR", "".to_string()), diff --git a/packages/cli/src/build/mod.rs b/packages/cli/src/build/mod.rs index 18b5a1bab2..166a5dcedb 100644 --- a/packages/cli/src/build/mod.rs +++ b/packages/cli/src/build/mod.rs @@ -1,3 +1,13 @@ +//! The core build module for `dx`, enabling building, bundling, and runtime hot-patching of Rust +//! applications. This module defines the entire end-to-end build process, including bundling for +//! all major platforms including Mac, Windows, Linux, iOS, Android, and WebAssembly. +//! +//! The bulk of the builder code is contained within the [`request`] module which establishes the +//! arguments and flow of the build process. The [`context`] module contains the context for the build +//! including status updates and build customization. The [`patch`] module contains the logic for +//! hot-patching Rust code through binary analysis and a custom linker. The [`builder`] module contains +//! the management of the ongoing build and methods to open the build as a running app. + mod builder; mod context; mod patch; diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index f956f15d8c..416b0a8ce9 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -1463,7 +1463,7 @@ fn parse_wasm_and_print_globals2() { #[test] fn hoists() { - let bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); + let bytes = include_bytes!("/Users/jonathankelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); let out_bytes = prepare_wasm_base_module(bytes).unwrap(); let out = walrus::Module::from_buffer(&out_bytes).unwrap(); @@ -1479,7 +1479,7 @@ fn hoists() { #[test] fn delta() { - let pre_bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); + let pre_bytes = include_bytes!("/Users/jonathankelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); // let prepared_out_bytes = prepare_wasm_base_module(pre_bytes).unwrap(); let prepared_out_bytes = pre_bytes; diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 2985878dfb..babedad0ae 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1,6 +1,6 @@ -//! # BuildRequest - the core of the build process +//! # [`BuildRequest`] - the core of the build process //! -//! The BuildRequest object is the core of the build process. It contains all the resolved arguments +//! The [`BuildRequest`] object is the core of the build process. It contains all the resolved arguments //! flowing in from the CLI, dioxus.toml, env vars, and the workspace. //! //! Every BuildRequest is tied to a given workspace and BuildArgs. For simplicity's sake, the BuildArgs @@ -348,7 +348,7 @@ use target_lexicon::{ Aarch64Architecture, Architecture, ArmArchitecture, BinaryFormat, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture, }; -use tempfile::NamedTempFile; +use tempfile::{NamedTempFile, TempDir}; use tokio::{io::AsyncBufReadExt, process::Command}; use toml_edit::Item; use uuid::Uuid; @@ -372,8 +372,11 @@ use uuid::Uuid; #[derive(Clone)] pub(crate) struct BuildRequest { pub(crate) workspace: Arc, + pub(crate) config: DioxusConfig, + pub(crate) crate_package: NodeId, + pub(crate) crate_target: krates::cm::Target, pub(crate) profile: String, @@ -421,6 +424,8 @@ pub(crate) struct BuildRequest { pub(crate) custom_linker: Option, + pub(crate) session_cache_dir: Arc, + pub(crate) link_args_file: Arc, pub(crate) link_err_file: Arc, @@ -672,6 +677,9 @@ impl BuildRequest { NamedTempFile::new() .context("Failed to create temporary file for rustc wrapper args")?, ); + let session_cache_dir = Arc::new( + TempDir::new().context("Failed to create temporary directory for session cache")?, + ); let extra_rustc_args = shell_words::split(&args.rustc_args.clone().unwrap_or_default()) .context("Failed to parse rustc args")?; @@ -691,6 +699,7 @@ impl BuildRequest { custom_linker, link_args_file, link_err_file, + session_cache_dir, rustc_wrapper_args_file, extra_rustc_args, extra_cargo_args: args.cargo_args.clone(), @@ -1573,8 +1582,6 @@ impl BuildRequest { continue; } - tracing::trace!("Adding object file {:?} to archive", name); - out_ar .append(&object_file.header().clone(), object_file) .context("Failed to add object file to archive")?; @@ -1865,70 +1872,62 @@ impl BuildRequest { // // This basically amounts of -all_load or --whole-archive, depending on the linker. // We just assume an ld-like interface on macos and a gnu-ld interface elsewhere. - match self.triple.operating_system { - // macOS/iOS use ld64 but through the `cc` interface. - OperatingSystem::Darwin(_) | OperatingSystem::MacOSX(_) => { - // cargo_args.push("-Clink-args=-Wl,-all_load".to_string()); - } - - // Linux and Android fit under this umbrella, both with the same clang-like entrypoint - // and the gnu-ld interface. - OperatingSystem::Linux => { - // cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); - } - - // If windows -Wl,--whole-archive is required since it follows gnu-ld convention. - // There might be other flags on windows - we haven't tested windows thoroughly. - // - // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170 - OperatingSystem::Windows => { - // cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); - } - - // if web, -Wl,--whole-archive is required since it follows gnu-ld convention. - // - // We also use --no-gc-sections and --export-table and --export-memory to push - // said symbols into the export table. - // - // We use --emit-relocs to build up a solid call graph. - // - // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld with a `-target wasm` flag): - // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld - // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld - // - // Note that we can't use --export-all, unfortunately, since some symbols are internal - // to wasm-bindgen and exporting them causes the JS generation to fail. - // - // We are basically replicating what emscripten does here with its dynamic linking - // approach where the MAIN_MODULE is very "fat" and exports the necessary arguments - // for the side modules to be linked in. This guide is really helpful: - // - // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md - // - // The trickiest one here is -Crelocation-model=pic, which forces data symbols - // into a GOT, making it possible to import them from the main module. - // - // 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::Wasi | OperatingSystem::Unknown - if self.platform == Platform::Web => - { - cargo_args.push("-Clink-arg=--no-gc-sections".into()); - cargo_args.push("-Clink-arg=--growable-table".into()); - cargo_args.push("-Clink-arg=--whole-archive".into()); - cargo_args.push("-Clink-arg=--export-table".into()); - cargo_args.push("-Clink-arg=--export-memory".into()); - cargo_args.push("-Clink-arg=--emit-relocs".into()); - cargo_args.push("-Clink-arg=--export=__stack_pointer".into()); - cargo_args.push("-Clink-arg=--export=__heap_base".into()); - cargo_args.push("-Clink-arg=--export=__data_end".into()); - cargo_args.push("-Crelocation-model=pic".into()); - } - - _ => { - tracing::error!("Thin linking is not supported on this platform - hot patching might not work properly."); - } + // + // macOS/iOS use ld64 but through the `cc` interface. + // cargo_args.push("-Clink-args=-Wl,-all_load".to_string()); + // + // Linux and Android fit under this umbrella, both with the same clang-like entrypoint + // and the gnu-ld interface. + // + // cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); + // + // If windows -Wl,--whole-archive is required since it follows gnu-ld convention. + // There might be other flags on windows - we haven't tested windows thoroughly. + // + // cargo_args.push("-Clink-args=-Wl,--whole-archive".to_string()); + // https://learn.microsoft.com/en-us/cpp/build/reference/wholearchive-include-all-library-object-files?view=msvc-170 + // + // ------------------------------------------------------------ + // + // if web, -Wl,--whole-archive is required since it follows gnu-ld convention. + // + // We also use --no-gc-sections and --export-table and --export-memory to push + // said symbols into the export table. + // + // We use --emit-relocs to build up a solid call graph. + // + // rust uses its own wasm-ld linker which can be found here (it's just gcc-ld with a `-target wasm` flag): + // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld + // - ~/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/gcc-ld/wasm-ld + // + // Note that we can't use --export-all, unfortunately, since some symbols are internal + // to wasm-bindgen and exporting them causes the JS generation to fail. + // + // We are basically replicating what emscripten does here with its dynamic linking + // approach where the MAIN_MODULE is very "fat" and exports the necessary arguments + // for the side modules to be linked in. This guide is really helpful: + // + // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md + // + // The trickiest one here is -Crelocation-model=pic, which forces data symbols + // into a GOT, making it possible to import them from the main module. + // + // 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. + if self.platform == Platform::Web + || self.triple.operating_system == OperatingSystem::Wasi + { + cargo_args.push("-Clink-arg=--no-gc-sections".into()); + cargo_args.push("-Clink-arg=--growable-table".into()); + cargo_args.push("-Clink-arg=--whole-archive".into()); + cargo_args.push("-Clink-arg=--export-table".into()); + cargo_args.push("-Clink-arg=--export-memory".into()); + cargo_args.push("-Clink-arg=--emit-relocs".into()); + cargo_args.push("-Clink-arg=--export=__stack_pointer".into()); + cargo_args.push("-Clink-arg=--export=__heap_base".into()); + cargo_args.push("-Clink-arg=--export=__data_end".into()); + cargo_args.push("-Crelocation-model=pic".into()); } } @@ -2407,9 +2406,7 @@ impl BuildRequest { /// /// The directory is specific for this app and might be pub(crate) fn session_cache_dir(&self) -> PathBuf { - self.internal_out_dir() - .join(self.executable_name()) - .join("session-cache") + self.session_cache_dir.path().to_path_buf() } /// Get the outdir specified by the Dioxus.toml, relative to the crate directory. @@ -3099,11 +3096,11 @@ impl BuildRequest { std::fs::write(&post_bindgen_wasm, modules.main.bytes)?; } - // // Make sure to optimize the main wasm file if requested or if bundle splitting - // if should_bundle_split || self.release { - // ctx.status_optimizing_wasm(); - // wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; - // } + // Make sure to optimize the main wasm file if requested or if bundle splitting + if should_bundle_split || self.release { + ctx.status_optimizing_wasm(); + wasm_opt::optimize(&post_bindgen_wasm, &post_bindgen_wasm, &wasm_opt_options).await?; + } // Make sure to register the main wasm file with the asset system assets.register_asset(&post_bindgen_wasm, AssetOptions::Unknown)?; @@ -3373,10 +3370,6 @@ impl BuildRequest { _ = std::fs::create_dir_all(&cache_dir); } - pub(crate) fn incremental_cache_dir(&self) -> PathBuf { - self.platform_dir().join("incremental-cache") - } - /// Check for tooling that might be required for this build. /// /// This should generally be only called on the first build since it takes time to verify the tooling diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index c3cf63e1d6..a9c1024923 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -62,7 +62,6 @@ impl Autoformat { // Default to formatting the project. let crate_dir = if let Some(package) = self.package { todo!() - // // TODO (matt): Do we need to use the entire `DioxusCrate` here? // let target_args = TargetArgs { // package: Some(package), // ..Default::default() diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index 6ba26bfc9e..3c50d7fe75 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform, Result, Workspace}; +use crate::{AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, Platform, Result, Workspace}; /// Run the project with the given arguments #[derive(Clone, Debug, Parser)] @@ -30,7 +30,14 @@ impl RunArgs { // } builder - .open(devserver_ip, open_address, Some(fullstack_ip), true, false) + .open( + devserver_ip, + open_address, + Some(fullstack_ip), + true, + false, + BuildId(0), + ) .await?; todo!(); diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index ca743c2a4f..6ad10bcd83 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -237,7 +237,6 @@ impl AppServer { let server_wait = OptionFuture::from(server.map(|s| s.wait())); let watcher_wait = self.watcher_rx.next(); - tokio::select! { // Wait for the client to finish client_update = client_wait => { @@ -514,6 +513,7 @@ impl AppServer { fullstack_address, open_browser, always_on_top, + BuildId(0), ) .await?; @@ -814,7 +814,9 @@ impl AppServer { aslr_reference, build_id, }) => { - tracing::debug!("Setting aslr_reference: {aslr_reference}"); + tracing::debug!( + "Setting aslr_reference: {aslr_reference} for build_id: {build_id}" + ); match build_id { 0 => { self.client.aslr_reference = Some(aslr_reference); @@ -1000,6 +1002,7 @@ impl AppServer { fullstack_address, false, false, + BuildId(1), ) .await?; } diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index d159dac8ff..37e1e48a67 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -20,8 +20,8 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config: // Set the control flow and check if any events need to be handled in the app itself app.tick(&window_event); - if let Some(ref mut f) = custom_event_handler { - f(&window_event, event_loop) + if let Some(ref mut callback) = custom_event_handler { + callback(&window_event, event_loop) } match window_event { diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 4da27082f9..669924b6c3 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -29,6 +29,7 @@ dioxus-liveview = { workspace = true, optional = true } dioxus-server = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true } dioxus-native = { workspace = true, optional = true } +dioxus_server_macro = { workspace = true, optional = true } manganis = { workspace = true, features = ["dioxus"], optional = true } dioxus-logger = { workspace = true, optional = true } warnings = { workspace = true, optional = true } @@ -89,8 +90,17 @@ web = [ ] ssr = ["dep:dioxus-ssr", "dioxus-config-macro/ssr"] liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"] -server = ["dep:dioxus-server", "ssr", "dioxus-liveview?/axum"] native = ["dep:dioxus-native"] # todo(jon): decompose the desktop crate such that "webview" is the default and native is opt-in +server = [ + "dep:dioxus-server", + "dep:dioxus_server_macro", + "dioxus_server_macro/server", + "ssr", + "dioxus-liveview?/axum", + "dioxus-fullstack/server", + "dep:dioxus-fullstack", + "fullstack" +] # This feature just disables the no-renderer-enabled warning third-party-renderer = [] From 2fdbf2395d54a8b3e98c6f4837a83e59bc1bc066 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 11:37:40 -0700 Subject: [PATCH 160/301] fix workspace compiles --- packages/server/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 285cbd4114..288a5404c1 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -75,7 +75,7 @@ tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"] } dioxus = { workspace = true, features = ["fullstack"] } [features] -default = ["devtools", "document", "server_fn/axum", "dioxus_server_macro/axum", "dioxus_server_macro/server", "dioxus-fullstack-hooks/server", "server_fn/ssr", "dep:tower-http"] +default = ["devtools", "document", "server_fn/axum", "dioxus-fullstack-hooks/server", "server_fn/ssr", "dep:tower-http"] # default = ["devtools", "document", "file_engine", "mounted"] devtools = ["dep:dioxus-devtools"] document = ["dep:dioxus-interpreter-js"] From fbef9487e462bfec9988a7ae906e65a670a3d46f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 12:34:34 -0700 Subject: [PATCH 161/301] clean up jump table work a bit --- example-projects/simple-web/src/main.rs | 26 +- packages/cli/src/build/builder.rs | 8 +- packages/cli/src/build/patch.rs | 316 ++++++++++++------------ packages/cli/src/build/request.rs | 2 +- packages/cli/src/serve/runner.rs | 49 ++-- 5 files changed, 212 insertions(+), 189 deletions(-) diff --git a/example-projects/simple-web/src/main.rs b/example-projects/simple-web/src/main.rs index e6b08db0db..1da1946483 100644 --- a/example-projects/simple-web/src/main.rs +++ b/example-projects/simple-web/src/main.rs @@ -15,17 +15,29 @@ fn app() -> Element { rsx! { h1 { "Hot patch serverfns!!!!!" } - EvalIt {} + h3 { "Set your favorite color" } + button { onclick: move |_| t += 1, "Click me: {t}" } + div { + EvalIt { color: "white" } + EvalIt { color: "red" } + EvalIt { color: "blue" } + EvalIt { color: "green" } + EvalIt { color: "magenta" } + EvalIt { color: "orange" } + } } } -fn EvalIt() -> Element { +#[component] +fn EvalIt(color: String) -> Element { rsx! { - button { - onclick: move |_| { - _ = dioxus::document::eval("window.document.body.style.backgroundColor = 'green';"); - }, - "eval!" + div { + button { + onclick: move |_| { + _ = dioxus::document::eval(&format!("window.document.body.style.backgroundColor = '{color}';")); + }, + "eval -> {color}" + } } } } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index cd85c41190..e49c850602 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -78,6 +78,7 @@ pub(crate) struct AppBuilder { /// The executables but with some extra entropy in their name so we can run two instances of the /// same app without causing collisions on the filesystem. pub entropy_app_exe: Option, + pub builds_opened: usize, // Metadata about the build that needs to be managed by watching build updates // used to render the TUI @@ -141,6 +142,7 @@ impl AppBuilder { compiled_crates: 0, expected_crates: 1, bundling_progress: 0.0, + builds_opened: 0, compile_start: Some(Instant::now()), aslr_reference: None, compile_end: None, @@ -283,7 +285,7 @@ impl AppBuilder { } /// Restart this builder with new build arguments. - pub(crate) fn fat_rebuild(&mut self) { + pub(crate) fn start_rebuild(&mut self, mode: BuildMode) { // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit // And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task self.abort_all(BuildStage::Restarting); @@ -292,7 +294,7 @@ impl AppBuilder { let request = self.build.clone(); let ctx = BuildContext { tx: self.tx.clone(), - mode: BuildMode::Fat, + mode, }; async move { request.build(&ctx).await } }); @@ -459,6 +461,8 @@ impl AppBuilder { self.child = Some(child); } + self.builds_opened += 1; + Ok(()) } diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 416b0a8ce9..6d2ec94243 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use itertools::Itertools; use memmap::{Mmap, MmapOptions}; use object::{ @@ -38,8 +38,8 @@ use wasmparser::{ use walrus::{ ir::{dfs_in_order, Visitor}, - ElementItems, ElementKind, FunctionId, FunctionKind, IdsToIndices, ImportKind, Module, - ModuleConfig, RawCustomSection, ValType, + ConstExpr, ElementItems, ElementKind, FunctionId, FunctionKind, IdsToIndices, ImportKind, + Module, ModuleConfig, RawCustomSection, ValType, }; pub fn create_jump_table( @@ -123,20 +123,162 @@ pub fn create_jump_table( /// In the web, our patchable functions are actually ifuncs /// /// We need to line up the ifuncs from the main module to the ifuncs in the patch. +/// +/// According to the dylink spec, there will be two sets of entries: +/// +/// - got.func: functions in the indirect function table +/// - got.mem: data objects in the data segments +/// +/// It doesn't seem like we can compile the base module to export these, sadly, so we're going +/// to manually satisfy them here, removing their need to be imported. +/// +/// https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md fn create_wasm_jump_table(original: &Path, patch: &Path) -> anyhow::Result { - tracing::info!("jumping {} to {}", original.display(), patch.display()); - let obj1_bytes = fs::read(original).context("Could not read original file")?; - let obj2_bytes = fs::read(patch).context("Could not read patch file")?; + let old_bytes = fs::read(original).context("Could not read original file")?; + let new_bytes = fs::read(patch).context("Could not read patch file")?; - let old = walrus::Module::from_buffer(&obj1_bytes)?; - let new = walrus::Module::from_buffer(&obj2_bytes)?; - - let old_raw_data = parse_bytes_to_data_segment(&obj1_bytes)?; - let new_raw_data = parse_bytes_to_data_segment(&obj2_bytes)?; + let old = walrus::Module::from_buffer(&old_bytes)?; + let mut new = walrus::Module::from_buffer(&new_bytes)?; + let old_raw_data = parse_bytes_to_data_segment(&old_bytes)?; let name_to_ifunc_old = collect_func_ifuncs(&old, &old_raw_data.symbols); - let name_to_ifunc_new = collect_func_ifuncs(&new, &new_raw_data.symbols); + let mut mems = vec![]; + let mut funcs = vec![]; + + // Collect all the GOT entries from the new module. + for t in new.imports.iter() { + match t.module.as_str() { + "GOT.func" => { + let Some(entry) = name_to_ifunc_old.get(t.name.as_str()).cloned() else { + let exists = old.exports.get_func(t.name.as_str()); + bail!("Expected to find GOT.func entry in ifunc table but it was missing: {} -> {exists:?}\nDid all symbols make it into the static lib?", t.name.as_str()) + }; + funcs.push((t.id(), entry)); + } + "GOT.mem" => mems.push(t.id()), + _ => {} + } + } + + // We need to satisfy the GOT.func imports of this side module. The GOT imports come from the wasm-ld + // implementation of the dynamic linking spec + // + // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md#imports + // + // Most importantly, these functions are functions meant to be called indirectly. In normal wasm + // code generation, only functions that Rust code references via pointers are given a slot in + // the indirection function table. The optimization here traditionally meaning that if a function + // can be called directly, then it doesn't need to be referenced indirectly and potentially inlined + // or dissolved during LTO. + // + // In our "fat build" setup, we aggregated all symbols from dependencies into a `dependencies.ar` file. + // By promoting these functions to the dynamic scope, we also prevent their inlining because the + // linker can still expect some form of interposition to happen, requiring the symbol *actually* + // exists. + // + // Our technique here takes advantage of that and the [`prepare_wasm_base_module`] function promotes + // every possible function to the indirect function table. This means that the GOT imports that + // `relocation-model=pic` synthesizes can reference the functions via the indirect function table + // even if they are not normally synthesized in regular wasm code generation. + // + // Normally, the dynaic linker setup would resolve GOT.func against the same GOT.func export in + // the main module, but we don't have that. Instead, we simply re-parse the main module, aggregate + // its ifunc table, and then resolve directly to the index in that table. + for (import_id, ifunc_index) in funcs { + let imp = new.imports.get(import_id); + let ImportKind::Global(id) = new.imports.get(import_id).kind else { + bail!("Expected GOT.func import to be a global"); + }; + + tracing::debug!( + "Importing GOT.func: {} -> real: {:?}", + imp.name, + ifunc_index + ); + + // "satisfying" the import means removing it from the import table and replacing its target + // value with a local global. + new.imports.delete(import_id); + new.globals.get_mut(id).kind = + walrus::GlobalKind::Local(ConstExpr::Value(walrus::ir::Value::I32(ifunc_index as i32))); + } + + // We need to satisfy the GOT.mem imports of this side module. The GOT.mem imports come from the wasm-ld + // implementation of the dynamic linking spec + // + // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md#imports + // + // Unlike the ifunc table, the GOT.mem imports do not need any additional post-processing of the + // base module to satisfy. Since our patching approach works but leveraging the experimental dynamic + // PIC support in rustc[wasm] and wasm-ld, we are using the GOT.mem imports as a way of identifying + // data segments that are present in the base module. + // + // Normally, the dynamic linker would synthesize corresponding GOT.mem exports in the main module, + // but since we're patching on-the-fly, this table will always be out-of-date. + // + // Instead, we use the symbol table from the base module to find the corresponding data symbols + // and then resolve the offset of the data segment in the main module. Using the symbol table + // can be somewhat finicky if the user compiled the code with a high-enough opt level that nukes + // the names of the data segments, but otherwise this system works well. + // + // We simply use the name of the import as a key into the symbol table and then its offset into + // its data segment as the value within the global. + for mem in mems { + let import = new.imports.get(mem); + let data_symbol_idx = *old_raw_data + .data_symbol_map + .get(import.name.as_str()) + .with_context(|| { + format!("Failed to find GOT.mem import by its name: {}", import.name) + })?; + let data_symbol = old_raw_data + .data_symbols + .get(&data_symbol_idx) + .context("Failed to find data symbol by its index")?; + let data = old + .data + .iter() + .nth(data_symbol.which_data_segment) + .context("Missing data segment in the main module")?; + + let offset = match data.kind { + walrus::DataKind::Active { + offset: ConstExpr::Value(walrus::ir::Value::I32(idx)), + .. + } => idx, + walrus::DataKind::Active { + offset: ConstExpr::Value(walrus::ir::Value::I64(idx)), + .. + } => idx as i32, + _ => { + bail!("Data segment of invalid table: {:?}", data.kind); + } + }; + + let ImportKind::Global(global_id) = import.kind else { + bail!("Expected GOT.mem import to be a global"); + }; + + // "satisfying" the import means removing it from the import table and replacing its target + // value with a local global. + new.imports.delete(mem); + new.globals.get_mut(global_id).kind = walrus::GlobalKind::Local(ConstExpr::Value( + walrus::ir::Value::I32(offset + data_symbol.segment_offset as i32), + )); + } + + // Update the wasm module on the fileystem to use the newly lifted version + let lib = patch.to_path_buf(); + std::fs::write(&lib, new.emit_wasm()).unwrap(); + + // And now assemble the jump table by mapping the old ifunc table to the new one, by name + // + // The ifunc_count will be passed to the dynamic loader so it can allocate the right amount of space + // in the indirect function table when loading the patch. + let new_raw_data = parse_bytes_to_data_segment(&new_bytes)?; + let name_to_ifunc_new = collect_func_ifuncs(&new, &new_raw_data.symbols); + let ifunc_count = name_to_ifunc_new.len() as u64; let mut map = AddressMap::default(); for (name, idx) in name_to_ifunc_new.iter() { if let Some(old_idx) = name_to_ifunc_old.get(name) { @@ -148,10 +290,10 @@ fn create_wasm_jump_table(original: &Path, patch: &Path) -> anyhow::Result(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap let offset = match offset { // Handle explicit offsets - walrus::ConstExpr::Value(value) => match value { + ConstExpr::Value(value) => match value { walrus::ir::Value::I32(idx) => *idx, walrus::ir::Value::I64(idx) => *idx as i32, _ => panic!(), @@ -182,9 +324,9 @@ fn collect_func_ifuncs<'a>(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap // Globals are usually imports and thus don't add a specific offset // ie the ifunc table is offset by a global, so we don't need to push the offset out - walrus::ConstExpr::Global(_) => 0, - walrus::ConstExpr::RefNull(_) => panic!(), - walrus::ConstExpr::RefFunc(_) => panic!(), + ConstExpr::Global(_) => 0, + ConstExpr::RefNull(_) => panic!(), + ConstExpr::RefFunc(_) => panic!(), }; match &el.items { @@ -221,7 +363,7 @@ fn fn_name_map<'a>(syms: &[SymbolInfo<'a>]) -> HashMap<&'a str, WrongFnIndex> { /// bypassing the dynamic linker. /// /// This is very similar to malware :) but it's not! -pub fn resolve_undefined( +pub fn create_undefined_symbol_stub( source_path: &Path, incrementals: &[PathBuf], triple: &Triple, @@ -560,122 +702,6 @@ fn collect_all_wasm_bindgen_funcs(module: &Module) -> HashSet { acc.funcs } -/// According to the dylink spec, there will be two sets of entries: -/// - got.func: functions in the indirect function table -/// - got.mem: data objects in the data segments -/// -/// It doesn't seem like we can compile the base module to export these, sadly, so we're going -/// to manually satisfy them here, removing their need to be imported. -/// -/// https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md -pub fn satisfy_got_imports(old_bytes: &[u8], new_bytes: &[u8]) -> Result> { - let old: walrus::Module = walrus::Module::from_buffer(old_bytes)?; - let mut new: walrus::Module = walrus::Module::from_buffer(new_bytes)?; - - let raw_data = parse_bytes_to_data_segment(&old_bytes)?; - let ifunc_map = collect_func_ifuncs(&old, &raw_data.symbols); - let global_map = collect_global_map(&old); - - let mut mems = vec![]; - let mut funcs = vec![]; - - // Collect the GOT func/mem entries - for t in new.imports.iter() { - match t.module.as_str() { - "GOT.func" => { - funcs.push(( - t.id(), - *ifunc_map.get(t.name.as_str()).unwrap_or_else(|| { - let exists = old.exports.get_func(t.name.as_str()); - panic!("failed to find GOT.func: {} -> {exists:?}", t.name.as_str()) - }), - )); - } - "GOT.mem" => mems.push(t.id()), - _ => {} - } - } - - // Satisfies the GOT.func imports. They exist as regular imports, but we need to make the indirect call - for (imp_id, val) in funcs { - let imp = new.imports.get(imp_id); - let global_id = match imp.kind { - ImportKind::Global(id) => id, - _ => todo!(), - }; - tracing::info!("Importing GOT.func: {} -> real: {:?}", imp.name, val); - new.globals.get_mut(global_id).kind = - walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32(val as i32))); - new.imports.delete(imp_id); - } - - // The got mem entries exist, but are hidden. we need to bind to their address directly, and - // remove the "GOT.data.internal" name - for mem in mems { - let imp = new.imports.get(mem); - let data_symbol_idx = *raw_data.data_symbol_map.get(imp.name.as_str()).unwrap(); - let data_symbol = raw_data.data_symbols.get(&data_symbol_idx).unwrap(); - let name = format!("GOT.data.internal.{}", imp.name); - let val_from_got = global_map.get(name.as_str()); - // let val_from_got = global_map.get(name.as_str()).unwrap_or_else(|| { - // let non_got = global_map.get(name.as_str()); - // panic!( - // "failed to find GOT.mem: {} -> non got: {non_got:?}", - // name.as_str() - // ) - // }); - // let offset = data_symbol.segment_offset as i32; - let data = old.data.iter().nth(data_symbol.which_data_segment).unwrap(); - let offset = match data.kind { - walrus::DataKind::Active { offset, .. } => match offset { - walrus::ConstExpr::Value(walrus::ir::Value::I32(idx)) => idx, - walrus::ConstExpr::Value(walrus::ir::Value::I64(idx)) => idx as i32, - _ => panic!(), - }, - _ => todo!(), - }; - - let global_id = match imp.kind { - ImportKind::Global(id) => id, - _ => todo!(), - }; - let data_offset = offset + data_symbol.segment_offset as i32; - - tracing::info!( - "GOT.mem: {} -> real: {:?} seg_offset: {} data_offset: {}\n({:?})", - name, - val_from_got, - data_symbol.segment_offset, - data_offset, - data_symbol - ); - - new.globals.get_mut(global_id).kind = walrus::GlobalKind::Local(walrus::ConstExpr::Value( - walrus::ir::Value::I32(data_offset), - )); - new.imports.delete(mem); - } - - Ok(new.emit_wasm()) -} - -fn collect_global_map(old: &Module) -> HashMap<&str, i32> { - let mut global_map = HashMap::new(); - - for global in old.globals.iter() { - if let Some(name) = &global.name { - if let walrus::GlobalKind::Local(walrus::ConstExpr::Value(walrus::ir::Value::I32( - value, - ))) = global.kind - { - global_map.insert(name.as_str(), value); - } - } - } - - global_map -} - /// Manually parse the data section from a wasm module /// /// We need to do this for data symbols because walrus doesn't provide the right range and offset @@ -713,26 +739,6 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result { let mut data_symbol_map = HashMap::new(); let mut code_symbol_map = BTreeMap::new(); for (index, symbol) in symbols.iter().enumerate() { - let name = match symbol { - SymbolInfo::Func { flags, index, name } => *name, - SymbolInfo::Data { - flags, - name, - symbol, - } => Some(*name), - SymbolInfo::Global { flags, index, name } => *name, - SymbolInfo::Section { flags, section } => None, - SymbolInfo::Event { flags, index, name } => *name, - SymbolInfo::Table { flags, index, name } => *name, - }; - - if let Some(name) = name { - // ZN59_$LT$dyn$u20$core..any..Any$u20$as$u20$core..fmt..Debug$GT$3fmt17h8ab3728dab9d43e8 - // if name.contains("core..any..Any$u20$as$u20$core..fmt..Debug$GT") { - // panic!("Found a core::any::Any symbol: {name} in {symbol:?}"); - // } - } - if let SymbolInfo::Func { name, index, .. } = symbol { if let Some(name) = name { code_symbol_map.insert(*name, *index as usize); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index babedad0ae..3dc1bf2754 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1225,7 +1225,7 @@ impl BuildRequest { // Android apps can take a long time to open, and a hot patch might've been issued in the interim, // making this hotpatch a failure. if self.platform != Platform::Web { - let stub_bytes = crate::build::resolve_undefined( + let stub_bytes = crate::build::create_undefined_symbol_stub( &self.main_exe(), &object_files, &self.triple, diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 6ad10bcd83..43da27f90b 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -12,7 +12,7 @@ use dioxus_core_types::HotReloadingContext; use dioxus_devtools_types::ClientMsg; use dioxus_devtools_types::HotReloadMsg; use dioxus_dx_wire_format::BuildStage; -use dioxus_html::HtmlCtx; +use dioxus_html::{r#use, HtmlCtx}; use dioxus_rsx::CallBody; use dioxus_rsx_hotreload::{ChangedRsx, HotReloadResult}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -59,10 +59,10 @@ pub(crate) struct AppServer { // Tracked state related to open builds and hot reloading pub(crate) applied_hot_reload_message: HotReloadMsg, - pub(crate) builds_opened: usize, pub(crate) file_map: HashMap, // Resolved args related to how we go about processing the rebuilds and logging + pub(crate) use_hotpatch_engine: bool, pub(crate) automatic_rebuilds: bool, pub(crate) interactive: bool, pub(crate) force_sequential: bool, @@ -194,8 +194,8 @@ impl AppServer { let mut runner = Self { file_map: Default::default(), applied_hot_reload_message: Default::default(), - builds_opened: 0, automatic_rebuilds: true, + use_hotpatch_engine: args.hot_patch, client, server, hot_reload, @@ -413,8 +413,11 @@ impl AppServer { } } - // For now, always run a patch instead of rsx hot-reload - if needs_full_rebuild || true { + // We decided to make the hotpatch engine drive *all* hotreloads, even if they are just RSX + // hot-reloads. This is a temporary measure to thoroughly test the hotpatch engine until + // we're comfortable with both co-existing. Keeping both would lead to two potential sources + // of errors, and we want to avoid that for now. + if needs_full_rebuild || self.use_hotpatch_engine { self.client.patch_rebuild(files.to_vec()); if let Some(server) = self.server.as_mut() { @@ -422,7 +425,7 @@ impl AppServer { } self.clear_hot_reload_changes(); - // self.clear_cached_rsx(); + self.clear_cached_rsx(); server.start_patch().await; server.send_patch_start().await; } else { @@ -453,11 +456,6 @@ impl AppServer { } } - pub(crate) fn rebuild_all(&mut self) { - self.client.fat_rebuild(); - self.server.as_mut().map(|s| s.fat_rebuild()); - } - /// Finally "bundle" this app and return a handle to it pub(crate) async fn open( &mut self, @@ -487,7 +485,7 @@ impl AppServer { && (self.server.as_ref().map(|s| s.stage == BuildStage::Success)).unwrap_or(true); if should_open { - if self.builds_opened == 0 { + if self.client.builds_opened == 0 { tracing::info!( "Build completed successfully in {:?}ms, launching app! 💫", time_taken.as_millis() @@ -503,7 +501,7 @@ impl AppServer { } // Start the new app before we kill the old one to give it a little bit of time - let open_browser = self.builds_opened == 0 && self.open_browser; + let open_browser = self.client.builds_opened == 0 && self.open_browser; let always_on_top = self.always_on_top; self.client @@ -517,8 +515,6 @@ impl AppServer { ) .await?; - self.builds_opened += 1; - // Give a second for the server to boot tokio::time::sleep(Duration::from_millis(300)).await; @@ -754,13 +750,6 @@ impl AppServer { // Rebase the wasm binary to be relocatable once the jump table is generated if triple.architecture == target_lexicon::Architecture::Wasm32 { - let old_bytes = std::fs::read(&original).unwrap(); - let new_bytes = std::fs::read(&jump_table.lib).unwrap(); - let res = crate::build::satisfy_got_imports(&old_bytes, &new_bytes).context( - "Couldn't satisfy GOT imports for WASM - are debug symbols being stripped?", - )?; - std::fs::write(&jump_table.lib, res).unwrap(); - // Make sure we use the dir relative to the public dir, so the web can load it as a proper URL // // ie we would've shipped `/Users/foo/Projects/dioxus/target/dx/project/debug/web/public/wasm/lib.wasm` @@ -979,8 +968,21 @@ impl AppServer { server.compiled_crates as f64 / server.expected_crates as f64 } + /// Perform a full rebuild of the app, equivalent to `cargo rustc` from scratch with no incremental + /// hot-patch engine integration. pub(crate) async fn full_rebuild(&mut self) { - self.rebuild_all(); + if self.use_hotpatch_engine { + self.client.start_rebuild(BuildMode::Fat); + self.server + .as_mut() + .map(|s| s.start_rebuild(BuildMode::Fat)); + } else { + self.client.start_rebuild(BuildMode::Base); + self.server + .as_mut() + .map(|s| s.start_rebuild(BuildMode::Base)); + } + self.clear_hot_reload_changes(); self.clear_cached_rsx(); } @@ -994,7 +996,6 @@ impl AppServer { tracing::debug!("Opening server build"); if let Some(server) = self.server.as_mut() { // server.cleanup().await; - server .open( devserver_ip, From 95de4e5c2ec1a191c8c95fda3d8e30344f6cacd7 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 12:40:13 -0700 Subject: [PATCH 162/301] clean up logging a bit --- packages/cli/src/serve/output.rs | 2 +- packages/cli/src/serve/runner.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 9223a90806..51100e5dda 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -333,7 +333,7 @@ impl Output { let msg = match res { Ok(msg) => msg, Err(err) => { - tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {}", platform, err); + tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {} -> {:?}", platform, err, text.as_str()); return; } }; diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 43da27f90b..6f3d850a47 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -768,7 +768,7 @@ impl AppServer { let changed_file = changed_files.first().unwrap(); tracing::info!( - "Hot-patching: {} in {:?}ms", + "Hot-patching: {} took {:?}ms", changed_file .strip_prefix(std::env::current_dir().unwrap()) .unwrap_or_else(|_| changed_file.as_path()) @@ -798,6 +798,10 @@ impl AppServer { .to_text() .context("client message not proper encoding")?; + if as_text.is_empty() { + return Ok(()); + } + match serde_json::from_str::(as_text) { Ok(ClientMsg::Initialize { aslr_reference, @@ -819,7 +823,7 @@ impl AppServer { } Ok(_client) => {} Err(err) => { - tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {}", Platform::Web, err); + tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {} -> {}", Platform::Web, err, as_text); } }; From 7924248f8da6457d539a125c04e793bba16fd413 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 14:40:53 -0700 Subject: [PATCH 163/301] open existing --- packages/cli/src/build/builder.rs | 23 +++++- packages/cli/src/build/patch.rs | 131 ------------------------------ packages/cli/src/build/request.rs | 6 +- packages/cli/src/serve/mod.rs | 63 ++++++-------- packages/cli/src/serve/runner.rs | 114 +++++++++----------------- 5 files changed, 89 insertions(+), 248 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index e49c850602..5bba4614c6 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -268,6 +268,25 @@ impl AppBuilder { } pub(crate) fn patch_rebuild(&mut self, changed_files: Vec) { + // We need the rustc args from the original build to pass to the new build + let Some(artifacts) = self.artifacts.as_ref().cloned() else { + tracing::warn!("Ignoring patch rebuild since there is no existing build."); + return; + }; + + // On web, our patches are fully relocatable, so we don't need to worry about ASLR, but + // for all other platforms, we need to use the ASLR reference to know where to insert the patch. + let aslr_reference = match self.aslr_reference { + Some(val) => val, + None if self.build.platform == Platform::Web => 0, + None => { + tracing::warn!( + "Ignoring hotpatch since there is no ASLR reference. Is the client connected?" + ); + return; + } + }; + // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit self.abort_all(BuildStage::Restarting); self.build_task = tokio::spawn({ @@ -276,8 +295,8 @@ impl AppBuilder { tx: self.tx.clone(), mode: BuildMode::Thin { changed_files, - rustc_args: self.artifacts.as_ref().unwrap().direct_rustc.clone(), - aslr_reference: self.aslr_reference, + rustc_args: artifacts.direct_rustc, + aslr_reference, }, }; async move { request.build(&ctx).await } diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 6d2ec94243..670e7d63cc 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -1437,137 +1437,6 @@ fn make_stub_file( .unwrap() } -#[test] -fn parse_wasm_and_print_globals() { - // let bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/libfullstack-hello-world-example-patch-1744937420042.wasm"); - // let module = walrus::Module::from_buffer(bytes).unwrap(); - // let globals = module.globals.iter().collect::>(); - // for g in globals { - // println!("{:?}: {:?}", g.name, g.kind); - // } -} - -const BADNAME: &str = "_ZN4core3ptr68drop_in_place$LT$alloc..boxed..Box$LT$dyn$u20$core..any..Any$GT$$GT$17hcd167959be12f848E"; - -#[test] -fn parse_wasm_and_print_globals2() { - let bytes = &[]; - // let bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/fullstack-hello-world-example_bg.wasm"); - let module = walrus::Module::from_buffer(bytes).unwrap(); - let func = module.funcs.by_name(BADNAME).unwrap(); - - let data = parse_bytes_to_data_segment(bytes).unwrap(); - let ifuncs = collect_func_ifuncs(&module, &data.symbols); - - // 55874 - println!("there are {} ifuncs", ifuncs.len()); - - let ifunc = ifuncs.get(BADNAME).unwrap(); - - println!("ifunc entry: {:?}", ifunc); -} - -#[test] -fn hoists() { - let bytes = include_bytes!("/Users/jonathankelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); - let out_bytes = prepare_wasm_base_module(bytes).unwrap(); - - let out = walrus::Module::from_buffer(&out_bytes).unwrap(); - let syms = parse_bytes_to_data_segment(&out_bytes).unwrap(); - let ifuncs = collect_func_ifuncs(&out, &syms.symbols); - - // 57001 - println!("there are {} ifuncs", ifuncs.len()); - - let ifunc = ifuncs.get(BADNAME).unwrap(); - println!("ifunc entry: {:?}", ifunc); -} - -#[test] -fn delta() { - let pre_bytes = include_bytes!("/Users/jonathankelley/Development/dioxus/target/wasm32-unknown-unknown/wasm-dev/fullstack-hello-world-example.wasm"); - // let prepared_out_bytes = prepare_wasm_base_module(pre_bytes).unwrap(); - - let prepared_out_bytes = pre_bytes; - - let pre_module = walrus::Module::from_buffer(prepared_out_bytes).unwrap(); - // let pre_module = walrus::Module::from_buffer(&prepared_out_bytes).unwrap(); - let pre_syms = parse_bytes_to_data_segment(prepared_out_bytes).unwrap(); - let pre_ifuncs = collect_func_ifuncs(&pre_module, &pre_syms.symbols); - let pre_name_map = fn_name_map(&pre_syms.symbols); - - // let bg_bytes = include_bytes!("/Users/jonkelley/Development/dioxus/target/dx/fullstack-hello-world-example/debug/web/public/wasm/fullstack-hello-world-example_bg.wasm"); - let bg_bytes = &[]; - let bg_module = walrus::Module::from_buffer(bg_bytes).unwrap(); - let bg_syms = parse_bytes_to_data_segment(bg_bytes).unwrap(); - let bg_ifuncs = collect_func_ifuncs(&bg_module, &bg_syms.symbols); - let bg_name_map = fn_name_map(&bg_syms.symbols); - - let pre_funcs = pre_module - .funcs - .iter() - .map(|f| (f.id().index(), f)) - .collect::>(); - - let bg_funcs = bg_module - .funcs - .iter() - .map(|f| (f.id().index(), f)) - .collect::>(); - - // for p in pre_ifuncs.iter() { - // if !bg_ifuncs.contains_key(*p.0) { - // println!("pre->: {:?}", p); - - // // let f = pre_funcs[&(*p.1 as usize)]; - // // println!("pre func: {:?}", f.name); - // } - // } - - let mut bad = 0; - for p in pre_name_map.iter() { - // if !bg_name_map.contains_key(*p.0) { - // println!("pre->: {:?}", p); - // } - - let pre = pre_funcs.get(&(p.1 .0 as usize)).unwrap(); - if pre.name.as_deref() != Some(*p.0) { - // println!("pre->: {:?} -> {:?}", pre.name, p.0); - bad += 1; - } - } - println!("bad: {bad}"); - println!("total: {}", bg_name_map.len()); - - let mut bad = 0; - for p in bg_name_map.iter() { - // if !bg_name_map.contains_key(*p.0) { - // println!("pre->: {:?}", p); - // } - - let bg = bg_funcs.get(&(p.1 .0 as usize)).unwrap(); - if bg.name.as_deref() != Some(*p.0) { - // println!("pre->: {:?} -> {:?}", pre.name, p.0); - bad += 1; - } - } - - println!("bad: {bad}"); - println!("total: {}", bg_name_map.len()); - - // for p in bg_ifuncs { - // if !pre_ifuncs.contains_key(p.0) { - // println!("bg->: {:?}", p); - // } - // } - - // // 57001 - // println!("there are {} ifuncs", ifuncs.len()); - - // let ifunc = ifuncs.get(BADNAME).unwrap(); - // println!("ifunc entry: {:?}", ifunc); -} - struct RawDataSection<'a> { data_range: Range, symbols: Vec>, diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 3dc1bf2754..ba00448f98 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -460,7 +460,7 @@ pub enum BuildMode { Thin { rustc_args: RustcArgs, changed_files: Vec, - aslr_reference: Option, + aslr_reference: u64, }, } @@ -1142,7 +1142,7 @@ impl BuildRequest { async fn write_patch( &self, ctx: &BuildContext, - aslr_reference: Option, + aslr_reference: u64, artifacts: &mut BuildArtifacts, ) -> Result<()> { tracing::debug!("Patching existing bundle"); @@ -1229,7 +1229,7 @@ impl BuildRequest { &self.main_exe(), &object_files, &self.triple, - aslr_reference.context("ASLR reference not found - is the client connected?")?, + aslr_reference, ) .expect("failed to resolve patch symbols"); diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index aa2b47dcb9..3ed4ecc813 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -56,7 +56,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> builder.app_name() ); - let err: Result<(), Error> = loop { + loop { // Draw the state of the server to the screen screen.render(&builder, &devserver); @@ -130,31 +130,24 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> BuilderUpdate::BuildFailed { err } => { tracing::error!("Build failed: {:#?}", err); } - BuilderUpdate::BuildReady { bundle } => { - match bundle.mode { - BuildMode::Thin { .. } => { - // We need to patch the app with the new bundle - let elapsed = - bundle.time_end.duration_since(bundle.time_start).unwrap(); - match builder.patch(&bundle).await { - Ok(jumptable) => devserver.send_patch(jumptable, elapsed).await, - Err(_) => {} + BuilderUpdate::BuildReady { bundle } => match bundle.mode { + BuildMode::Thin { .. } => { + let elapsed = + bundle.time_end.duration_since(bundle.time_start).unwrap(); + match builder.patch(&bundle).await { + Ok(jumptable) => devserver.send_patch(jumptable, elapsed).await, + Err(err) => { + tracing::error!("Failed to patch app: {err}"); } } - BuildMode::Base | BuildMode::Fat => { - builder - .open( - bundle, - devserver.devserver_address(), - devserver.proxied_server_address(), - devserver.displayed_address(), - &mut devserver, - ) - .await - .inspect_err(|e| tracing::error!("Failed to open app: {}", e)); - } } - } + BuildMode::Base | BuildMode::Fat => { + _ = builder + .open(bundle, &mut devserver) + .await + .inspect_err(|e| tracing::error!("Failed to open app: {}", e)); + } + }, BuilderUpdate::StdoutReceived { msg } => { screen.push_stdio(platform, msg, tracing::Level::INFO); } @@ -201,20 +194,16 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> ) } - ServeUpdate::Exit { error } => match error { - Some(err) => break Err(anyhow::anyhow!("{}", err).into()), - None => break Ok(()), - }, - } - }; - - _ = builder.cleanup_all().await; - _ = devserver.shutdown().await; - _ = screen.shutdown(); + ServeUpdate::Exit { error } => { + _ = builder.cleanup_all().await; + _ = devserver.shutdown().await; + _ = screen.shutdown(); - if let Err(err) = err { - eprintln!("Exiting with error: {}", err); + match error { + Some(err) => return Err(anyhow::anyhow!("{}", err).into()), + None => return Ok(()), + } + } + } } - - Ok(()) } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 6f3d850a47..a02b3eaf28 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -460,18 +460,9 @@ impl AppServer { pub(crate) async fn open( &mut self, artifacts: BuildArtifacts, - devserver_ip: SocketAddr, - fullstack_address: Option, - displayed_address: Option, devserver: &mut WebServer, ) -> Result<()> { - // Add some cute logging - let time_taken = artifacts - .time_end - .duration_since(artifacts.time_start) - .unwrap(); - - // Make sure to save artifacts... + // Make sure to save artifacts regardless of if we're opening the app or not match artifacts.platform { Platform::Server => { if let Some(server) = self.server.as_mut() { @@ -485,6 +476,11 @@ impl AppServer { && (self.server.as_ref().map(|s| s.stage == BuildStage::Success)).unwrap_or(true); if should_open { + let time_taken = artifacts + .time_end + .duration_since(artifacts.time_start) + .unwrap(); + if self.client.builds_opened == 0 { tracing::info!( "Build completed successfully in {:?}ms, launching app! 💫", @@ -494,26 +490,7 @@ impl AppServer { tracing::info!("Build completed in {:?}ms", time_taken.as_millis()); } - // Always open the server first after the client has been built - if let Some(_server) = self.server.as_ref() { - self.open_server(devserver_ip, fullstack_address, displayed_address) - .await?; - } - - // Start the new app before we kill the old one to give it a little bit of time - let open_browser = self.client.builds_opened == 0 && self.open_browser; - let always_on_top = self.always_on_top; - - self.client - .open( - devserver_ip, - displayed_address, - fullstack_address, - open_browser, - always_on_top, - BuildId(0), - ) - .await?; + self.open_existing(devserver).await?; // Give a second for the server to boot tokio::time::sleep(Duration::from_millis(300)).await; @@ -527,29 +504,40 @@ impl AppServer { /// Open an existing app bundle, if it exists pub(crate) async fn open_existing(&mut self, devserver: &WebServer) -> Result<()> { + let devserver_ip = devserver.devserver_address(); let fullstack_address = devserver.proxied_server_address(); + let displayed_address = devserver.displayed_address(); - // let fullstack_address = devserver.proxied_server_address(); - - // if let Some(runner) = self.running.as_mut() { - // runner.soft_kill().await; - // runner - // .open( - // devserver.devserver_address(), - // devserver.displayed_address(), - // fullstack_address, - // true, - // ) - // .await?; - // } - - todo!(); - // if let Some(runner) = self.running.as_mut() { - // runner.soft_kill().await; - // runner - // .open(devserver.devserver_address(), fullstack_address, true) - // .await?; - // } + // Always open the server first after the client has been built + if let Some(server) = self.server.as_mut() { + tracing::debug!("Opening server build"); + server.soft_kill().await; + server + .open( + devserver_ip, + displayed_address, + fullstack_address, + false, + false, + BuildId(1), + ) + .await?; + } + + // Start the new app before we kill the old one to give it a little bit of time + let open_browser = self.client.builds_opened == 0 && self.open_browser; + let always_on_top = self.always_on_top; + self.client.soft_kill().await; + self.client + .open( + devserver_ip, + displayed_address, + fullstack_address, + open_browser, + always_on_top, + BuildId(0), + ) + .await?; Ok(()) } @@ -990,30 +978,6 @@ impl AppServer { self.clear_hot_reload_changes(); self.clear_cached_rsx(); } - - async fn open_server( - &mut self, - devserver_ip: SocketAddr, - fullstack_address: Option, - displayed_address: Option, - ) -> Result<()> { - tracing::debug!("Opening server build"); - if let Some(server) = self.server.as_mut() { - // server.cleanup().await; - server - .open( - devserver_ip, - displayed_address, - fullstack_address, - false, - false, - BuildId(1), - ) - .await?; - } - - Ok(()) - } } /// Bind a listener to any point and return it From 601378ca0a822379e57d484762d745d88ba54e0f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 15:04:38 -0700 Subject: [PATCH 164/301] fixup some commands --- packages/cli/src/build/builder.rs | 13 ++++---- packages/cli/src/cli/autoformat.rs | 21 ++++++------ packages/cli/src/cli/check.rs | 39 +++++++++------------- packages/cli/src/cli/run.rs | 53 ++++++++++++++++-------------- packages/cli/src/serve/runner.rs | 20 +++++------ 5 files changed, 68 insertions(+), 78 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 5bba4614c6..15676db84e 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -181,13 +181,12 @@ impl AppBuilder { StderrReceived { msg } }, Some(status) = OptionFuture::from(self.child.as_mut().map(|f| f.wait())) => { - match status { - Ok(status) => { - self.child = None; - ProcessExited { status } - }, - Err(_err) => todo!("handle error in process joining?"), - } + // Panicking here is on purpose. If the task crashes due to a JoinError (a panic), + // we want to propagate that panic up to the serve controller. + let status = status.unwrap(); + self.child = None; + + ProcessExited { status } } }; diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index a9c1024923..f112ca4cf7 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -1,4 +1,5 @@ use super::*; +use crate::Workspace; use anyhow::Context; use dioxus_autofmt::{IndentOptions, IndentType}; use rayon::prelude::*; @@ -61,16 +62,16 @@ impl Autoformat { } else { // Default to formatting the project. let crate_dir = if let Some(package) = self.package { - todo!() - // let target_args = TargetArgs { - // package: Some(package), - // ..Default::default() - // }; - // let dx_crate = DioxusCrate::new(&target_args) - // .await - // .context("failed to parse crate graph")?; - - // Cow::Owned(dx_crate.crate_dir()) + let workspace = Workspace::current().await?; + let dx_crate = workspace + .find_main_package(Some(package)) + .context("Failed to find package")?; + workspace.krates[dx_crate] + .manifest_path + .parent() + .unwrap() + .to_path_buf() + .into() } else { Cow::Borrowed(Path::new(".")) }; diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/cli/check.rs index e578d3da71..88ad2880eb 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/cli/check.rs @@ -4,9 +4,11 @@ //! https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs use super::*; +use crate::Workspace; use anyhow::Context; use futures_util::{stream::FuturesUnordered, StreamExt}; use std::path::Path; +use walkdir::WalkDir; /// Check the Rust files in the project for issues. #[derive(Clone, Debug, Parser)] @@ -51,10 +53,15 @@ async fn check_file_and_report(path: PathBuf) -> Result<()> { /// /// Doesn't do mod-descending, so it will still try to check unreachable files. TODO. async fn check_project_and_report(krate: &BuildArgs) -> Result<()> { - todo!("check_project_and_report"); - // let mut files_to_check = vec![dioxus_crate.main_source_file()]; - // collect_rs_files(&dioxus_crate.crate_dir(), &mut files_to_check); - // check_files_and_report(files_to_check).await + let workspace = Workspace::current().await?; + let dioxus_crate = workspace.find_main_package(krate.package.clone())?; + let dioxus_crate = &workspace.krates[dioxus_crate]; + let mut files_to_check = vec![]; + collect_rs_files( + dioxus_crate.manifest_path.parent().unwrap().as_std_path(), + &mut files_to_check, + ); + check_files_and_report(files_to_check).await } /// Check a list of files and report the issues. @@ -107,26 +114,10 @@ async fn check_files_and_report(files_to_check: Vec) -> Result<()> { } fn collect_rs_files(folder: &Path, files: &mut Vec) { - let Ok(folder) = folder.read_dir() else { - return; - }; - - // load the gitignore - for entry in folder { - let Ok(entry) = entry else { - continue; - }; - - let path = entry.path(); - - if path.is_dir() { - collect_rs_files(&path, files); - } - - if let Some(ext) = path.extension() { - if ext == "rs" { - files.push(path); - } + let dir = WalkDir::new(folder).follow_links(true).into_iter(); + for entry in dir.flatten() { + if entry.path().extension() == Some("rs".as_ref()) { + files.push(entry.path().to_path_buf()); } } } diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index 3c50d7fe75..a730ce6fbb 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -1,46 +1,49 @@ use super::*; -use crate::{AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, Platform, Result, Workspace}; +use crate::{ + serve::serve_all, AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, Platform, Result, + Workspace, +}; /// Run the project with the given arguments #[derive(Clone, Debug, Parser)] pub(crate) struct RunArgs { /// Information about the target to build #[clap(flatten)] - pub(crate) build_args: BuildArgs, + pub(crate) build_args: ServeArgs, } impl RunArgs { pub(crate) async fn run(self) -> Result { let workspace = Workspace::current().await?; - let build = BuildRequest::new(&self.build_args, workspace) - .await - .context("error building project")?; + // let build = BuildRequest::new(&self.build_args, workspace) + // .await + // .context("error building project")?; - let mut builder = AppBuilder::start(&build, BuildMode::Base)?; - let artifacts = builder.finish_build().await?; + // let mut builder = AppBuilder::start(&build, BuildMode::Base)?; + // let artifacts = builder.finish_build().await?; - let devserver_ip = "127.0.0.1:8081".parse().unwrap(); - let fullstack_ip = "127.0.0.1:8080".parse().unwrap(); - let mut open_address = None; + // let devserver_ip = "127.0.0.1:8081".parse().unwrap(); + // let fullstack_ip = "127.0.0.1:8080".parse().unwrap(); + // let mut open_address = None; - todo!(); - // if build.platform == Platform::Web || build.fullstack { - // tracing::info!("Serving at: {}", fullstack_ip); - // } + // todo!(); + // // if build.platform == Platform::Web || build.fullstack { + // // tracing::info!("Serving at: {}", fullstack_ip); + // // } - builder - .open( - devserver_ip, - open_address, - Some(fullstack_ip), - true, - false, - BuildId(0), - ) - .await?; + // builder + // .open( + // devserver_ip, + // open_address, + // Some(fullstack_ip), + // true, + // false, + // BuildId(0), + // ) + // .await?; - todo!(); + // todo!(); // // Run the app, but mostly ignore all the other messages // // They won't generally be emitted // loop { diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index a02b3eaf28..020d865ba9 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -12,7 +12,7 @@ use dioxus_core_types::HotReloadingContext; use dioxus_devtools_types::ClientMsg; use dioxus_devtools_types::HotReloadMsg; use dioxus_dx_wire_format::BuildStage; -use dioxus_html::{r#use, HtmlCtx}; +use dioxus_html::HtmlCtx; use dioxus_rsx::CallBody; use dioxus_rsx_hotreload::{ChangedRsx, HotReloadResult}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -963,17 +963,13 @@ impl AppServer { /// Perform a full rebuild of the app, equivalent to `cargo rustc` from scratch with no incremental /// hot-patch engine integration. pub(crate) async fn full_rebuild(&mut self) { - if self.use_hotpatch_engine { - self.client.start_rebuild(BuildMode::Fat); - self.server - .as_mut() - .map(|s| s.start_rebuild(BuildMode::Fat)); - } else { - self.client.start_rebuild(BuildMode::Base); - self.server - .as_mut() - .map(|s| s.start_rebuild(BuildMode::Base)); - } + let build_mode = match self.use_hotpatch_engine { + true => BuildMode::Fat, + false => BuildMode::Base, + }; + + self.client.start_rebuild(build_mode.clone()); + self.server.as_mut().map(|s| s.start_rebuild(build_mode)); self.clear_hot_reload_changes(); self.clear_cached_rsx(); From bafa5b89697b7fca5c20b506910f48d513452ee0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 15:14:21 -0700 Subject: [PATCH 165/301] open existing browser integration --- packages/cli/src/serve/mod.rs | 2 +- packages/cli/src/serve/runner.rs | 10 +++++++--- packages/cli/src/workspace.rs | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 3ed4ecc813..a11e149fd7 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -173,7 +173,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> } ServeUpdate::OpenApp => { - if let Err(err) = builder.open_existing(&devserver).await { + if let Err(err) = builder.open_all(&devserver, true).await { tracing::error!("Failed to open app: {err}") } } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 020d865ba9..287171e1a5 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -490,7 +490,8 @@ impl AppServer { tracing::info!("Build completed in {:?}ms", time_taken.as_millis()); } - self.open_existing(devserver).await?; + let open_browser = self.client.builds_opened == 0 && self.open_browser; + self.open_all(devserver, open_browser).await?; // Give a second for the server to boot tokio::time::sleep(Duration::from_millis(300)).await; @@ -503,7 +504,11 @@ impl AppServer { } /// Open an existing app bundle, if it exists - pub(crate) async fn open_existing(&mut self, devserver: &WebServer) -> Result<()> { + pub(crate) async fn open_all( + &mut self, + devserver: &WebServer, + open_browser: bool, + ) -> Result<()> { let devserver_ip = devserver.devserver_address(); let fullstack_address = devserver.proxied_server_address(); let displayed_address = devserver.displayed_address(); @@ -525,7 +530,6 @@ impl AppServer { } // Start the new app before we kill the old one to give it a little bit of time - let open_browser = self.client.builds_opened == 0 && self.open_browser; let always_on_top = self.always_on_top; self.client.soft_kill().await; self.client diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index 6870fdf9f5..a4db881552 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -83,6 +83,8 @@ impl Workspace { } pub fn wasm_ld(&self) -> PathBuf { + // 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 self.sysroot .join("lib") .join("rustlib") From 7c7b2675644fef8ad41eb972f4e712ca2def13de Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 21 Apr 2025 17:30:17 -0700 Subject: [PATCH 166/301] wire up the new dx serve client/server syntax --- example-projects/simple-web/src/main.rs | 4 + packages/cli/src/build/builder.rs | 6 +- packages/cli/src/build/context.rs | 6 ++ packages/cli/src/build/request.rs | 6 +- packages/cli/src/build/web.rs | 63 +---------- packages/cli/src/cli/build.rs | 5 - packages/cli/src/cli/bundle.rs | 2 + packages/cli/src/cli/chained.rs | 132 +----------------------- packages/cli/src/cli/serve.rs | 14 +-- packages/cli/src/serve/mod.rs | 7 +- packages/cli/src/serve/runner.rs | 120 +++++++++++++-------- packages/cli/src/serve/server.rs | 32 ++---- packages/cli/src/workspace.rs | 2 + packages/dx-wire-format/src/lib.rs | 1 + 14 files changed, 119 insertions(+), 281 deletions(-) diff --git a/example-projects/simple-web/src/main.rs b/example-projects/simple-web/src/main.rs index 1da1946483..86016a1277 100644 --- a/example-projects/simple-web/src/main.rs +++ b/example-projects/simple-web/src/main.rs @@ -20,6 +20,10 @@ fn app() -> Element { div { EvalIt { color: "white" } EvalIt { color: "red" } + EvalIt { color: "yellow" } + EvalIt { color: "cyan" } + EvalIt { color: "purple" } + EvalIt { color: "black" } EvalIt { color: "blue" } EvalIt { color: "green" } EvalIt { color: "magenta" } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 15676db84e..387bd2ae92 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -385,9 +385,9 @@ impl AppBuilder { tracing::error!(?err, json = ?StructuredOutput::Error { message: err.to_string() }); return Err(err); } - BuilderUpdate::StdoutReceived { msg } => {} - BuilderUpdate::StderrReceived { msg } => {} - BuilderUpdate::ProcessExited { status } => {} + BuilderUpdate::StdoutReceived { .. } => {} + BuilderUpdate::StderrReceived { .. } => {} + BuilderUpdate::ProcessExited { .. } => {} } } } diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index 9b6887a890..cb4ab8798b 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -120,6 +120,12 @@ impl BuildContext { }); } + pub(crate) fn status_starting_link(&self) { + _ = self.tx.unbounded_send(BuilderUpdate::Progress { + stage: BuildStage::Linking, + }); + } + pub(crate) fn status_copied_asset( progress: &UnboundedSender, current: usize, diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index ba00448f98..1b93e92872 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1522,6 +1522,8 @@ impl BuildRequest { ctx: &BuildContext, artifacts: &BuildArtifacts, ) -> 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::>(); @@ -2847,9 +2849,7 @@ impl BuildRequest { } // Check if any of the features enables the "fullstack" feature - // todo!("check if any of the features enables the fullstack feature"); - - false + self.workspace.has_dioxus_feature("fullstack") } /// todo(jon): use handlebars templates instead of these prebaked templates diff --git a/packages/cli/src/build/web.rs b/packages/cli/src/build/web.rs index d6cd44f9cb..95a949bc74 100644 --- a/packages/cli/src/build/web.rs +++ b/packages/cli/src/build/web.rs @@ -1,10 +1,9 @@ +use crate::error::Result; use dioxus_cli_config::format_base_path_meta_element; use dioxus_cli_opt::AssetManifest; use manganis::AssetOptions; - -use crate::error::Result; use std::fmt::Write; -use std::path::{Path, PathBuf}; +use std::path::Path; use super::BuildRequest; @@ -46,7 +45,6 @@ impl BuildRequest { self.replace_template_placeholders(&assets, &mut html); let title = self.config.web.app.title.clone(); - replace_or_insert_before("{app_title}", " // Replace the app_name if we find it anywhere standalone *html = html.replace("{app_name}", app_name); } - - fn send_resource_deprecation_warning(&self, paths: Vec, variant: ResourceType) { - const RESOURCE_DEPRECATION_MESSAGE: &str = r#"The `web.resource` config has been deprecated in favor of head components and will be removed in a future release. Instead of including assets in the config, you can include assets with the `asset!` macro and add them to the head with `document::Link` and `Script` components."#; - - let replacement_components = paths - .iter() - .map(|path| { - let path = if path.exists() { - path.to_path_buf() - } else { - // If the path is absolute, make it relative to the current directory before we join it - // The path is actually a web path which is relative to the root of the website - let path = path.strip_prefix("/").unwrap_or(path); - let asset_dir_path = self - .legacy_asset_dir() - .map(|dir| dir.join(path).canonicalize()); - - if let Some(Ok(absolute_path)) = asset_dir_path { - let absolute_crate_root = self.crate_dir().canonicalize().unwrap(); - PathBuf::from("./") - .join(absolute_path.strip_prefix(absolute_crate_root).unwrap()) - } else { - path.to_path_buf() - } - }; - match variant { - ResourceType::Style => { - format!(" Stylesheet {{ href: asset!(\"{}\") }}", path.display()) - } - ResourceType::Script => { - format!(" Script {{ src: asset!(\"{}\") }}", path.display()) - } - } - }) - .collect::>(); - let replacement_components = format!("rsx! {{\n{}\n}}", replacement_components.join("\n")); - let section_name = match variant { - ResourceType::Style => "web.resource.style", - ResourceType::Script => "web.resource.script", - }; - - tracing::warn!( - "{RESOURCE_DEPRECATION_MESSAGE}\nTo migrate to head components, remove `{section_name}` and include the following rsx in your root component:\n```rust\n{replacement_components}\n```" - ); - } -} - -enum ResourceType { - Style, - Script, } /// Replace a string or insert the new contents before a marker diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index e8e114fd5a..7db27e5ab1 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -75,11 +75,6 @@ pub(crate) struct BuildArgs { #[clap(long)] pub(crate) rustc_args: Option, - /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] - #[clap(long)] - #[serde(default)] - pub(crate) force_sequential: bool, - /// Skip collecting assets from dependencies [default: false] #[clap(long)] #[serde(default)] diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index b5ec121f17..6b8267b1e2 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -305,6 +305,8 @@ impl Bundle { Ok(bundles) } + // During SSG, just serve the static files instead of running the server + // _ => builds[0].fullstack && !self.build_arguments.ssg, pub(crate) async fn pre_render_static_routes(server_exe: &Path) -> anyhow::Result<()> { // Use the address passed in through environment variables or default to localhost:9999. We need // to default to a value that is different than the CLI default address to avoid conflicts diff --git a/packages/cli/src/cli/chained.rs b/packages/cli/src/cli/chained.rs index 148d61adcc..80555a8e07 100644 --- a/packages/cli/src/cli/chained.rs +++ b/packages/cli/src/cli/chained.rs @@ -8,21 +8,10 @@ use serde::{de::DeserializeOwned, Deserialize}; #[derive(Debug, Clone)] pub struct ChainedCommand { /// Specific Variant. - inner: T, + pub inner: T, /// Enum containing `Self` variants, in other words possible follow-up commands. - next: Option>, -} - -impl ChainedCommand -where - T: Args, - U: Subcommand, -{ - fn commands(self) -> Vec { - let mut commands = vec![]; - commands - } + pub next: Option>, } impl Args for ChainedCommand @@ -72,120 +61,3 @@ where unimplemented!() } } - -impl<'de, T: Deserialize<'de>, U: Deserialize<'de>> Deserialize<'de> for ChainedCommand { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - todo!() - } -} - -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[derive(Debug, Parser)] -// struct TestCli { -// #[clap(long)] -// top: Option, - -// #[command(subcommand)] -// cmd: TopCmd, -// } - -// /// Launch a specific target -// /// -// /// You can specify multiple targets using `@client --args` syntax. -// #[derive(Debug, Parser)] -// struct ServeCommand { -// #[clap(flatten)] -// args: Target, - -// #[command(subcommand)] -// targets: TopCmd, -// } - -// #[derive(Debug, Subcommand, Clone)] -// enum TopCmd { -// Serve { -// #[clap(subcommand)] -// cmd: Cmd, -// }, -// } - -// /// Launch a specific target -// #[derive(Debug, Subcommand, Clone)] -// #[command(subcommand_precedence_over_arg = true)] -// enum Cmd { -// /// Specify the arguments for the client build -// #[clap(name = "client")] -// Client(ReClap), - -// /// Specify the arguments for the server build -// #[clap(name = "server")] -// Server(ReClap), - -// /// Specify the arguments for any number of additional targets -// #[clap(name = "target")] -// Target(ReClap), -// } - -// #[derive(Clone, Args, Debug)] -// struct Target { -// #[arg(short, long)] -// profile: Option, - -// #[arg(short, long)] -// target: Option, - -// #[arg(short, long)] -// bin: Option, -// } - -// #[test] -// fn test_parse_args() { -// let args = r#" -// dx serve -// @client --release -// @server --target wasm32 -// @target --bin mybin -// @target --bin mybin -// @target --bin mybin -// @target --bin mybin -// "# -// .trim() -// .split_ascii_whitespace(); - -// let cli = TestCli::parse_from(args); - -// dbg!(&cli); - -// match cli.cmd { -// TopCmd::Serve { cmd } => { -// let mut next = Some(cmd); - -// // let mut next = cmd.cmd; -// while let Some(cmd) = next { -// // println!("{cmd:?}"); -// // could use enum_dispatch -// next = match cmd { -// Cmd::Client(rec) => { -// // -// (rec.next).map(|d| *d) -// } -// Cmd::Server(rec) => { -// // -// (rec.next).map(|d| *d) -// } -// Cmd::Target(rec) => { -// // -// (rec.next).map(|d| *d) -// } -// } -// } -// } -// } -// } -// } diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index ccbed04292..a650a9af32 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -75,6 +75,10 @@ pub(crate) struct ServeArgs { #[arg(long, default_value_t = true)] pub(crate) hot_patch: bool, + /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] + #[clap(long)] + pub(crate) force_sequential: bool, + /// Enable fullstack mode [default: false] /// /// This is automatically detected from `dx serve` if the "fullstack" feature is enabled by default. @@ -119,20 +123,16 @@ pub(crate) struct ServeArgs { } /// Launch a specific target -#[derive(Debug, Subcommand, Clone, Deserialize)] +#[derive(Debug, Subcommand, Clone)] #[command(subcommand_precedence_over_arg = true)] pub(crate) enum TargetCmd { /// Specify the arguments for the client build #[clap(name = "client")] - Client(ChainedCommand), + Client(ChainedCommand), /// Specify the arguments for the server build #[clap(name = "server")] - Server(ChainedCommand), - - /// Specify the arguments for any number of additional targets - #[clap(name = "crate")] - Target(ChainedCommand), + Server(ChainedCommand), } impl ServeArgs { diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index a11e149fd7..99d74f702b 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -1,7 +1,4 @@ -use crate::{ - AppBuilder, BuildMode, BuildRequest, BuilderUpdate, Error, Platform, Result, ServeArgs, - TraceController, TraceSrc, -}; +use crate::{AppBuilder, BuildMode, BuilderUpdate, Result, ServeArgs, TraceController}; mod ansi_buffer; mod output; @@ -120,8 +117,6 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> devserver.new_build_update(&update).await; // And then open the app if it's ready - // todo: there might be more things to do here that require coordination with other pieces of the CLI - // todo: maybe we want to shuffle the runner around to send an "open" command instead of doing that match update { BuilderUpdate::Progress { .. } => {} BuilderUpdate::CompilerMessage { message } => { diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 287171e1a5..caf9911fae 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -1,7 +1,7 @@ -use super::{AppBuilder, ServeUpdate, WebServer, SELF_IP}; +use super::{AppBuilder, ServeUpdate, WebServer}; use crate::{ - AddressArguments, BuildArtifacts, BuildId, BuildMode, BuildRequest, Platform, Result, - ServeArgs, TraceSrc, Workspace, + chained::ChainedCommand, AddressArguments, BuildArgs, BuildArtifacts, BuildId, BuildMode, + BuildRequest, Platform, Result, ServeArgs, TargetCmd, TraceSrc, Workspace, }; use anyhow::Context; use axum::extract::ws::Message as WsMessage; @@ -92,7 +92,7 @@ impl AppServer { // Resolve the simpler args let interactive = args.is_interactive_tty(); - let force_sequential = args.build_arguments.force_sequential; + let force_sequential = args.force_sequential; let cross_origin_policy = args.cross_origin_policy; // These come from the args but also might come from the workspace settings @@ -113,9 +113,9 @@ impl AppServer { .always_on_top .unwrap_or_else(|| workspace.settings.always_on_top.unwrap_or(true)); - // Use 0.0.0.0 as the default address if none is specified - this will let us expose the - // devserver to the network (for other devices like phones/embedded) - let devserver_bind_ip = args.address.addr.unwrap_or(SELF_IP); + // Use 127.0.0.1 as the default address if none is specified. + // If the user wants to export on the network, they can use `0.0.0.0` instead. + let devserver_bind_ip = args.address.addr.unwrap_or(WebServer::SELF_IP); // If the user specified a port, use that, otherwise use any available port, preferring 8080 let devserver_port = args @@ -127,53 +127,87 @@ impl AppServer { let (watcher_tx, watcher_rx) = futures_channel::mpsc::unbounded(); let watcher = create_notify_watcher(watcher_tx.clone(), wsl_file_poll_interval as u64); - // Now resolve the builds that we need to. - // These come from the args, but we'd like them to come from the `TargetCmd` chained object - // - // The process here is as follows: - // - // - Create the BuildRequest for the primary target - // - If that BuildRequest is "fullstack", then add the client features - // - If that BuildRequest is "fullstack", then also create a BuildRequest for the server - // with the server features - // - // This involves modifying the BuildRequest to add the client features and server features - // only if we can properly detect that it's a fullstack build. Careful with this, since - // we didn't build BuildRequest to be generally mutable. - let client = BuildRequest::new(&args.build_arguments, workspace.clone()).await?; + let mut fullstack = false; let mut server = None; + let client = match args.targets { + // A simple `dx serve` command with no explicit targets + None => { + // Now resolve the builds that we need to. + // These come from the args, but we'd like them to come from the `TargetCmd` chained object + // + // The process here is as follows: + // + // - Create the BuildRequest for the primary target + // - If that BuildRequest is "fullstack", then add the client features + // - If that BuildRequest is "fullstack", then also create a BuildRequest for the server + // with the server features + // + // This involves modifying the BuildRequest to add the client features and server features + // only if we can properly detect that it's a fullstack build. Careful with this, since + // we didn't build BuildRequest to be generally mutable. + let client = BuildRequest::new(&args.build_arguments, workspace.clone()).await?; + + // Make sure we set the fullstack platform so we actually build the fullstack variant + // Users need to enable "fullstack" in their default feature set. + // todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled + // + // Now we need to resolve the client features + fullstack = client.fullstack_feature_enabled() || args.fullstack.unwrap_or(false); + if fullstack { + let mut build_args = args.build_arguments.clone(); + build_args.platform = Some(Platform::Server); + + let _server = BuildRequest::new(&build_args, workspace.clone()).await?; + + // ... todo: add the server features to the server build + // ... todo: add the client features to the client build + // // Make sure we have a server feature if we're building a fullstack app + if args.fullstack.unwrap_or_default() && args.server_features.is_empty() { + return Err(anyhow::anyhow!("Fullstack builds require a server feature on the target crate. Add a `server` feature to the crate and try again.").into()); + } - // Now we need to resolve the client features - let fullstack = client.fullstack_feature_enabled() || args.fullstack.unwrap_or(false); - if fullstack { - let mut build_args = args.build_arguments.clone(); - build_args.platform = Some(Platform::Server); + server = Some(_server); + } - let _server = BuildRequest::new(&build_args, workspace.clone()).await?; + client + } - // ... todo: add the server features to the server build - // ... todo: add the client features to the client build - // // Make sure we have a server feature if we're building a fullstack app - // if self.fullstack && self.server_features.is_empty() { - // return Err(anyhow::anyhow!("Fullstack builds require a server feature on the target crate. Add a `server` feature to the crate and try again.").into()); - // } + // A command in the form of: + // ``` + // dx serve \ + // client --package frontend \ + // server --package backend + // ``` + Some(cmd) => { + let mut client_args_ = None; + let mut server_args_ = None; + let mut cmd_outer = Some(Box::new(cmd)); + while let Some(cmd) = cmd_outer.take() { + match *cmd { + TargetCmd::Client(cmd_) => { + client_args_ = Some(cmd_.inner); + cmd_outer = cmd_.next; + } + TargetCmd::Server(cmd) => { + server_args_ = Some(cmd.inner); + cmd_outer = cmd.next; + } + } + } - // // Make sure we set the fullstack platform so we actually build the fullstack variant - // // Users need to enable "fullstack" in their default feature set. - // // todo(jon): fullstack *could* be a feature of the app, but right now we're assuming it's always enabled - // let fullstack = args.fullstack.unwrap_or_default() - // || client.workspace.has_dioxus_feature("fullstack"); + if let Some(server_ags) = server_args_ { + server = Some(BuildRequest::new(&server_ags, workspace.clone()).await?); + } - server = Some(_server); - } + BuildRequest::new(&client_args_.unwrap(), workspace.clone()).await? + } + }; // All servers will end up behind us (the devserver) but on a different port // This is so we can serve a loading screen as well as devtools without anything particularly fancy let should_proxy_port = match client.platform { Platform::Server => true, _ => fullstack, - // During SSG, just serve the static files instead of running the server - // _ => builds[0].fullstack && !self.build_arguments.ssg, }; let proxied_port = should_proxy_port @@ -291,8 +325,6 @@ impl AppServer { } } - tracing::debug!("Files changed: {files:?}"); - ServeUpdate::FilesChanged { files } } diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 1be93e2454..db1d81c767 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -1,7 +1,6 @@ use crate::{ - config::WebHttpsConfig, - serve::{ServeArgs, ServeUpdate}, - BuildRequest, BuildStage, BuilderUpdate, Platform, Result, TraceSrc, + config::WebHttpsConfig, serve::ServeUpdate, BuildStage, BuilderUpdate, Platform, Result, + TraceSrc, }; use anyhow::Context; use axum::{ @@ -19,7 +18,6 @@ use axum::{ routing::{get, get_service}, Extension, Router, }; -use axum_server::tls_rustls::RustlsConfig; use dioxus_devtools_types::{DevserverMsg, HotReloadMsg}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{ @@ -30,11 +28,10 @@ use futures_util::{ use hyper::HeaderMap; use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, convert::Infallible, fs, io, net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}, - path::{Path, PathBuf}, + path::Path, sync::{Arc, RwLock}, time::Duration, }; @@ -46,7 +43,7 @@ use tower_http::{ ServiceBuilderExt, }; -use super::{AppBuilder, AppServer}; +use super::AppServer; /// The webserver that serves statics assets (if fullstack isn't already doing that) and the websocket /// communication layer that we use to send status updates and hotreloads to the client. @@ -68,10 +65,9 @@ pub(crate) struct WebServer { platform: Platform, } -pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); -// pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - impl WebServer { + pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + /// Start the development server. /// This will set up the default http server if there's no server specified (usually via fullstack). /// @@ -86,6 +82,7 @@ impl WebServer { let devserver_bind_ip = runner.devserver_bind_ip; let devserver_port = runner.devserver_port; let proxied_port = runner.proxied_port; + let devserver_exposed_ip = devserver_bind_ip; let devserver_bind_address = SocketAddr::new(devserver_bind_ip, devserver_port); let listener = std::net::TcpListener::bind(devserver_bind_address).with_context(|| { @@ -94,15 +91,6 @@ impl WebServer { ) })?; - // // If the IP is 0.0.0.0, we need to get the actual IP of the machine - // // This will let ios/android/network clients connect to the devserver - // let devserver_exposed_ip = if devserver_bind_ip == SELF_IP { - // local_ip_address::local_ip().unwrap_or(devserver_bind_ip) - // } else { - // devserver_bind_ip - // }; - let devserver_exposed_ip = devserver_bind_ip; - let proxied_address = proxied_port.map(|port| SocketAddr::new(devserver_exposed_ip, port)); // Set up the router with some shared state that we'll update later to reflect the current state of the build @@ -266,9 +254,9 @@ impl WebServer { self.send_reload_failed().await; self.send_build_status().await; } - BuilderUpdate::StdoutReceived { msg } => {} - BuilderUpdate::StderrReceived { msg } => {} - BuilderUpdate::ProcessExited { status } => {} + BuilderUpdate::StdoutReceived { .. } => {} + BuilderUpdate::StderrReceived { .. } => {} + BuilderUpdate::ProcessExited { .. } => {} } } diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index a4db881552..052493592c 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -21,6 +21,7 @@ pub struct Workspace { } impl Workspace { + /// Load the workspace from the current directory. This is cached and will only be loaded once. pub async fn current() -> Result> { static WS: Mutex>> = Mutex::new(None); @@ -73,6 +74,7 @@ impl Workspace { Ok(workspace) } + #[allow(unused)] pub fn rust_lld(&self) -> PathBuf { self.sysroot .join("lib") diff --git a/packages/dx-wire-format/src/lib.rs b/packages/dx-wire-format/src/lib.rs index 52288d745a..8196e35313 100644 --- a/packages/dx-wire-format/src/lib.rs +++ b/packages/dx-wire-format/src/lib.rs @@ -57,6 +57,7 @@ pub enum BuildStage { RunningBindgen, SplittingBundle, OptimizingWasm, + Linking, Hotpatching, CopyingAssets { current: usize, From 3dad41ce6c569d5d9c7904a48610ba4494487154 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 22 Apr 2025 00:21:09 -0700 Subject: [PATCH 167/301] fixup `dx run` command --- packages/cli/src/build/context.rs | 6 +- packages/cli/src/cli/chained.rs | 3 +- packages/cli/src/cli/run.rs | 157 +++++++++++++++++++++--------- packages/cli/src/cli/serve.rs | 8 +- packages/cli/src/serve/runner.rs | 45 +++++---- packages/core/src/virtual_dom.rs | 22 ----- 6 files changed, 150 insertions(+), 91 deletions(-) diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index cb4ab8798b..1f54adc7da 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -21,7 +21,11 @@ pub type ProgressTx = UnboundedSender; pub type ProgressRx = UnboundedReceiver; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct BuildId(pub usize); +pub struct BuildId(pub(crate) usize); +impl BuildId { + pub const CLIENT: Self = Self(0); + pub const SERVER: Self = Self(1); +} #[allow(clippy::large_enum_variant)] pub enum BuilderUpdate { diff --git a/packages/cli/src/cli/chained.rs b/packages/cli/src/cli/chained.rs index 80555a8e07..58027ec51d 100644 --- a/packages/cli/src/cli/chained.rs +++ b/packages/cli/src/cli/chained.rs @@ -1,5 +1,4 @@ -use clap::{ArgMatches, Args, FromArgMatches, Parser, Subcommand}; -use serde::{de::DeserializeOwned, Deserialize}; +use clap::{ArgMatches, Args, FromArgMatches, Subcommand}; // https://github.com/clap-rs/clap/issues/2222#issuecomment-2524152894 // diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index a730ce6fbb..782702aa7e 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -1,66 +1,133 @@ use super::*; use crate::{ - serve::serve_all, AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, Platform, Result, - Workspace, + serve::{AppServer, ServeUpdate, WebServer}, + BuilderUpdate, Platform, Result, }; +use dioxus_dx_wire_format::BuildStage; /// Run the project with the given arguments +/// +/// This is a shorthand for `dx serve` with interactive mode and hot-reload disabled. #[derive(Clone, Debug, Parser)] pub(crate) struct RunArgs { /// Information about the target to build #[clap(flatten)] - pub(crate) build_args: ServeArgs, + pub(crate) args: ServeArgs, } impl RunArgs { - pub(crate) async fn run(self) -> Result { - let workspace = Workspace::current().await?; + pub(crate) async fn run(mut self) -> Result { + // Override the build arguments, leveraging our serve infrastructure. + // + // We want to turn off the fancy stuff like the TUI, watcher, and hot-reload, but leave logging + // and other things like the devserver on. + self.args.hot_patch = Some(false); + self.args.interactive = Some(false); + self.args.hot_reload = Some(false); + self.args.watch = Some(false); - // let build = BuildRequest::new(&self.build_args, workspace) - // .await - // .context("error building project")?; + let mut builder = AppServer::start(self.args).await?; + let mut devserver = WebServer::start(&builder)?; - // let mut builder = AppBuilder::start(&build, BuildMode::Base)?; - // let artifacts = builder.finish_build().await?; + loop { + let msg = tokio::select! { + msg = builder.wait() => msg, + msg = devserver.wait() => msg, + }; - // let devserver_ip = "127.0.0.1:8081".parse().unwrap(); - // let fullstack_ip = "127.0.0.1:8080".parse().unwrap(); - // let mut open_address = None; + match msg { + // Wait for logs from the build engine + // These will cause us to update the screen + // We also can check the status of the builds here in case we have multiple ongoing builds + ServeUpdate::BuilderUpdate { id, update } => { + let platform = builder.get_build(id).unwrap().build.platform; - // todo!(); - // // if build.platform == Platform::Web || build.fullstack { - // // tracing::info!("Serving at: {}", fullstack_ip); - // // } + // And then update the websocketed clients with the new build status in case they want it + devserver.new_build_update(&update).await; - // builder - // .open( - // devserver_ip, - // open_address, - // Some(fullstack_ip), - // true, - // false, - // BuildId(0), - // ) - // .await?; + // And then open the app if it's ready + match update { + BuilderUpdate::BuildReady { bundle } => { + _ = builder + .open(bundle, &mut devserver) + .await + .inspect_err(|e| tracing::error!("Failed to open app: {}", e)); - // todo!(); - // // Run the app, but mostly ignore all the other messages - // // They won't generally be emitted - // loop { - // match builder.wait().await { - // HandleUpdate::StderrReceived { platform, msg } => { - // tracing::info!("[{platform}]: {msg}") - // } - // HandleUpdate::StdoutReceived { platform, msg } => { - // tracing::info!("[{platform}]: {msg}") - // } - // HandleUpdate::ProcessExited { platform, status } => { - // builder.cleanup().await; - // tracing::info!("[{platform}]: process exited with status: {status:?}"); - // break; - // } - // } - // } + if platform == Platform::Web { + tracing::info!( + "Serving app at http://{}:{}", + builder.devserver_bind_ip, + builder.devserver_port + ); + } + } + BuilderUpdate::Progress { stage } => match stage { + BuildStage::Initializing => { + tracing::info!("[{platform}] Initializing build") + } + BuildStage::Starting { .. } => {} + BuildStage::InstallingTooling => {} + BuildStage::Compiling { + current, + total, + krate, + } => { + tracing::info!("[{platform}] Compiling {krate} ({current}/{total})",) + } + BuildStage::RunningBindgen => { + tracing::info!("[{platform}] Running WASM bindgen") + } + BuildStage::SplittingBundle => {} + BuildStage::OptimizingWasm => { + tracing::info!("[{platform}] Optimizing WASM with `wasm-opt`") + } + BuildStage::Linking => tracing::info!("Linking app"), + BuildStage::Hotpatching => todo!(), + BuildStage::CopyingAssets { + current, + total, + path, + } => tracing::info!( + "[{platform}] Copying asset {} ({current}/{total})", + path.display(), + ), + BuildStage::Bundling => tracing::info!("[{platform}] Bundling app"), + BuildStage::RunningGradle => { + tracing::info!("[{platform}] Running Gradle") + } + BuildStage::Success => {} + BuildStage::Failed => {} + BuildStage::Aborted => {} + BuildStage::Restarting => {} + BuildStage::CompressingAssets => {} + _ => {} + }, + BuilderUpdate::CompilerMessage { message } => { + print!("{}", message); + } + BuilderUpdate::BuildFailed { err } => { + tracing::error!("Build failed: {:#?}", err); + } + BuilderUpdate::StdoutReceived { msg } => { + tracing::info!("[{platform}] {msg}"); + } + BuilderUpdate::StderrReceived { msg } => { + tracing::error!("[{platform}] {msg}"); + } + BuilderUpdate::ProcessExited { status } => { + if !status.success() { + tracing::error!( + "Application [{platform}] exited with error: {status}" + ); + } + + break; + } + } + } + _ => {} + } + } Ok(StructuredOutput::Success) } diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index a650a9af32..cddcccf13c 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -72,8 +72,12 @@ pub(crate) struct ServeArgs { /// Enable Rust hot-patching instead of full rebuilds [default: false] /// /// This is quite experimental and may lead to unexpected segfaults or crashes in development. - #[arg(long, default_value_t = true)] - pub(crate) hot_patch: bool, + #[arg(long, default_missing_value = "false")] + pub(crate) hot_patch: Option, + + /// Watch the filesystem for changes and trigger a rebuild [default: true] + #[clap(long, default_missing_value = "true")] + pub(crate) watch: Option, /// This flag only applies to fullstack builds. By default fullstack builds will run the server and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false] #[clap(long)] diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index caf9911fae..c823b2bf0a 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -71,6 +71,7 @@ pub(crate) struct AppServer { pub(crate) wsl_file_poll_interval: u16, pub(crate) always_on_top: bool, pub(crate) fullstack: bool, + pub(crate) watch_fs: bool, // resolve args related to the webserver pub(crate) devserver_port: u16, @@ -214,7 +215,9 @@ impl AppServer { .then(|| get_available_port(devserver_bind_ip, None)) .flatten(); - let build_mode = match args.hot_patch { + let watch_fs = args.watch.unwrap_or(true); + let use_hotpatch_engine = args.hot_patch.unwrap_or(false); + let build_mode = match use_hotpatch_engine { true => BuildMode::Fat, false => BuildMode::Base, }; @@ -229,7 +232,8 @@ impl AppServer { file_map: Default::default(), applied_hot_reload_message: Default::default(), automatic_rebuilds: true, - use_hotpatch_engine: args.hot_patch, + watch_fs, + use_hotpatch_engine, client, server, hot_reload, @@ -249,16 +253,19 @@ impl AppServer { fullstack, }; - // Spin up the notify watcher - // When builds load though, we're going to parse their depinfo and add the paths to the watcher - runner.watch_filesystem(); - - // todo(jon): this might take a while so we should try and background it, or make it lazy somehow - // we could spawn a thread to search the FS and then when it returns we can fill the filemap - // in testing, if this hits a massive directory, it might take several seconds with no feedback. - // really, we should be using depinfo to get the files that are actually used, but the depinfo file might not be around yet - // todo(jon): see if we can just guess the depinfo file before it generates. might be stale but at least it catches most of the files - runner.load_rsx_filemap(); + // Only register the hot-reload stuff if we're watching the filesystem + if runner.watch_fs { + // Spin up the notify watcher + // When builds load though, we're going to parse their depinfo and add the paths to the watcher + runner.watch_filesystem(); + + // todo(jon): this might take a while so we should try and background it, or make it lazy somehow + // we could spawn a thread to search the FS and then when it returns we can fill the filemap + // in testing, if this hits a massive directory, it might take several seconds with no feedback. + // really, we should be using depinfo to get the files that are actually used, but the depinfo file might not be around yet + // todo(jon): see if we can just guess the depinfo file before it generates. might be stale but at least it catches most of the files + runner.load_rsx_filemap(); + } Ok(runner) } @@ -275,14 +282,14 @@ impl AppServer { // Wait for the client to finish client_update = client_wait => { ServeUpdate::BuilderUpdate { - id: BuildId(0), + id: BuildId::CLIENT, update: client_update, } } Some(server_update) = server_wait => { ServeUpdate::BuilderUpdate { - id: BuildId(1), + id: BuildId::SERVER, update: server_update, } } @@ -556,7 +563,7 @@ impl AppServer { fullstack_address, false, false, - BuildId(1), + BuildId::SERVER, ) .await?; } @@ -571,7 +578,7 @@ impl AppServer { fullstack_address, open_browser, always_on_top, - BuildId(0), + BuildId::CLIENT, ) .await?; @@ -605,9 +612,9 @@ impl AppServer { } pub(crate) fn get_build(&self, id: BuildId) -> Option<&AppBuilder> { - match id.0 { - 0 => Some(&self.client), - 1 => self.server.as_ref(), + match id { + BuildId::CLIENT => Some(&self.client), + BuildId::SERVER => self.server.as_ref(), _ => None, } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 3313f1345b..7d7276d984 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -785,28 +785,6 @@ impl Drop for VirtualDom { /// Yield control back to the async scheduler. This is used to give the scheduler a chance to run other pending work. Or cancel the task if the client has disconnected. async fn yield_now() { - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let abc = 123; - let mut yielded = false; std::future::poll_fn::<(), _>(move |cx| { if !yielded { From 0978a1b7ebd35f96f38bd9d1a681477027550cf6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 22 Apr 2025 11:05:06 -0700 Subject: [PATCH 168/301] bring back some old functionality --- packages/cli/src/build/builder.rs | 14 +- packages/cli/src/build/patch.rs | 21 -- packages/cli/src/build/request.rs | 20 +- packages/cli/src/cli/bundle.rs | 5 +- packages/cli/src/cli/mod.rs | 2 +- packages/cli/src/config/app.rs | 1 - packages/cli/src/serve/runner.rs | 271 ++++++++++++------------ packages/subsecond/subsecond/src/lib.rs | 12 +- 8 files changed, 162 insertions(+), 184 deletions(-) diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 387bd2ae92..4cb29a447a 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -415,20 +415,28 @@ impl AppBuilder { dioxus_cli_config::DEVSERVER_PORT_ENV, devserver_ip.port().to_string(), ), + ( + dioxus_cli_config::APP_TITLE_ENV, + krate.config.web.app.title.clone(), + ), ( dioxus_cli_config::SESSION_CACHE_DIR, self.build.session_cache_dir().display().to_string(), ), (dioxus_cli_config::BUILD_ID, build_id.0.to_string()), + ( + dioxus_cli_config::ALWAYS_ON_TOP_ENV, + always_on_top.to_string(), + ), // unset the cargo dirs in the event we're running `dx` locally // since the child process will inherit the env vars, we don't want to confuse the downstream process ("CARGO_MANIFEST_DIR", "".to_string()), ("RUST_BACKTRACE", "1".to_string()), ]; - // if let Some(base_path) = &krate.config.web.app.base_path { - // envs.push((dioxus_cli_config::ASSET_ROOT_ENV, base_path.clone())); - // } + if let Some(base_path) = &krate.config.web.app.base_path { + envs.push((dioxus_cli_config::ASSET_ROOT_ENV, base_path.clone())); + } if let Some(env_filter) = env::var_os("RUST_LOG").and_then(|e| e.into_string().ok()) { envs.push(("RUST_LOG", env_filter)); diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 670e7d63cc..4c8fd6bd7a 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -298,14 +298,6 @@ fn create_wasm_jump_table(original: &Path, patch: &Path) -> anyhow::Result(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap<&'a str, i32> { - // name -> index - // we want to export *all* these functions - let namemap = fn_name_map(syms); - let mut indexes_to_names = HashMap::>::new(); - for (n, i) in namemap.iter() { - indexes_to_names.entry(*i).or_default().push(*n); - } - let mut offsets = HashMap::new(); for el in m.elements.iter() { @@ -343,19 +335,6 @@ fn collect_func_ifuncs<'a>(m: &'a Module, syms: &'a [SymbolInfo<'a>]) -> HashMap offsets } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -struct WrongFnIndex(u32); -fn fn_name_map<'a>(syms: &[SymbolInfo<'a>]) -> HashMap<&'a str, WrongFnIndex> { - let all_funcs = syms - .iter() - .flat_map(|sym| match sym { - SymbolInfo::Func { index, name, .. } => Some((name.unwrap(), WrongFnIndex(*index))), - _ => None, - }) - .collect::>(); - all_funcs -} - /// Resolve the undefined symbols in the incrementals against the original binary, returning an object /// file that can be linked along the incrementals. /// diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 1b93e92872..029c909ccd 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -206,7 +206,7 @@ //! Notice that we *could* feasibly build this ourselves :) //! //! ### Windows: -//! https://superuser.com/questions/749447/creating-a-single-file-executable-from-a-directory-in-windows +//! //! Windows does not provide an AppImage format, so instead we're going build the same folder //! structure as an AppImage, but when distributing, we'll create a .exe that embeds the resources //! as an embedded .zip file. When the app runs, it will implicitly unzip its resources into the @@ -309,11 +309,11 @@ //! The idea here is that we can run any of the programs in the same way that they're deployed. //! //! ## Bundle structure links -//! - apple: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle -//! - appimage: https://docs.appimage.org/packaging-guide/manual.html#ref-manual +//! - apple: ://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle> +//! - appimage: ://docs.appimage.org/packaging-guide/manual.html#ref-manual> //! //! ## Extra links -//! - xbuild: https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs +//! - xbuild: use super::{android_tools, AndroidTools, BuildContext, BuildId}; use crate::{ @@ -1491,7 +1491,7 @@ impl BuildRequest { /// is taken into account. This is the same work that the rust compiler does when assembling /// staticlibs. /// - /// https://github.com/rust-lang/rust/blob/191df20fcad9331d3a948aa8e8556775ec3fe69d/compiler/rustc_codegen_ssa/src/back/link.rs#L448 + /// /// /// Since we're going to be passing these to the linker, we need to make sure and not provide any /// weird files (like the rmeta) file that rustc generates. @@ -1499,11 +1499,11 @@ impl BuildRequest { /// We discovered the need for this after running into issues with wasm-ld not being able to /// handle the rmeta file. /// - /// https://github.com/llvm/llvm-project/issues/55786 + /// /// /// Also, crates might not drag in all their dependent code. The monorphizer won't lift trait-based generics: /// - /// https://github.com/rust-lang/rust/blob/191df20fcad9331d3a948aa8e8556775ec3fe69d/compiler/rustc_monomorphize/src/collector.rs + /// /// /// When Rust normally handles this, it uses the +whole-archive directive which adjusts how the rlib /// is written to disk. @@ -2057,7 +2057,7 @@ impl BuildRequest { /// Get an estimate of the number of units in the crate. If nightly rustc is not available, this /// will return an estimate of the number of units in the crate based on cargo metadata. /// - /// TODO: always use https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph once it is stable + /// TODO: always use once it is stable async fn get_unit_count_estimate(&self, ctx: &BuildContext) -> usize { // Try to get it from nightly if let Ok(count) = self.get_unit_count(ctx).await { @@ -3179,7 +3179,7 @@ impl BuildRequest { /// Run bundleRelease and return the path to the `.aab` file /// - /// https://stackoverflow.com/questions/57072558/whats-the-difference-between-gradlewassemblerelease-gradlewinstallrelease-and + /// pub(crate) async fn android_gradle_bundle(&self) -> Result { let output = Command::new(self.gradle_exe()?) .arg("bundleRelease") @@ -3488,7 +3488,7 @@ impl BuildRequest { /// This varies by distro, so we just do nothing for now. /// /// Eventually, we want to check for the prereqs for wry/tao as outlined by tauri: - /// https://tauri.app/start/prerequisites/ + /// async fn verify_linux_tooling(&self) -> Result<()> { Ok(()) } diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index 6b8267b1e2..0dd27e5c48 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -305,9 +305,10 @@ impl Bundle { Ok(bundles) } - // During SSG, just serve the static files instead of running the server - // _ => builds[0].fullstack && !self.build_arguments.ssg, pub(crate) async fn pre_render_static_routes(server_exe: &Path) -> anyhow::Result<()> { + // During SSG, just serve the static files instead of running the server + // _ => builds[0].fullstack && !self.build_arguments.ssg, + // Use the address passed in through environment variables or default to localhost:9999. We need // to default to a value that is different than the CLI default address to avoid conflicts let ip = server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); diff --git a/packages/cli/src/cli/mod.rs b/packages/cli/src/cli/mod.rs index cdc4d93f5d..4fc2cf0c42 100644 --- a/packages/cli/src/cli/mod.rs +++ b/packages/cli/src/cli/mod.rs @@ -92,7 +92,7 @@ pub(crate) enum Commands { Config(config::Config), /// Build the assets for a specific target. - #[clap(name = "build_assets")] + #[clap(name = "assets")] BuildAssets(build_assets::BuildAssets), } diff --git a/packages/cli/src/config/app.rs b/packages/cli/src/config/app.rs index 02f1874300..77fdcb9146 100644 --- a/packages/cli/src/config/app.rs +++ b/packages/cli/src/config/app.rs @@ -3,7 +3,6 @@ use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct ApplicationConfig { - #[serde(default)] pub(crate) asset_dir: Option, #[serde(default)] diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index c823b2bf0a..c3d1545138 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -1,14 +1,13 @@ use super::{AppBuilder, ServeUpdate, WebServer}; use crate::{ - chained::ChainedCommand, AddressArguments, BuildArgs, BuildArtifacts, BuildId, BuildMode, - BuildRequest, Platform, Result, ServeArgs, TargetCmd, TraceSrc, Workspace, + BuildArtifacts, BuildId, BuildMode, BuildRequest, Platform, Result, ServeArgs, TargetCmd, + TraceSrc, Workspace, }; use anyhow::Context; use axum::extract::ws::Message as WsMessage; use dioxus_core::internal::{ HotReloadTemplateWithLocation, HotReloadedTemplate, TemplateGlobalKey, }; -use dioxus_core_types::HotReloadingContext; use dioxus_devtools_types::ClientMsg; use dioxus_devtools_types::HotReloadMsg; use dioxus_dx_wire_format::BuildStage; @@ -16,27 +15,23 @@ use dioxus_html::HtmlCtx; use dioxus_rsx::CallBody; use dioxus_rsx_hotreload::{ChangedRsx, HotReloadResult}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use futures_util::future::OptionFuture; use futures_util::StreamExt; -use futures_util::{future::OptionFuture, pin_mut}; -use ignore::gitignore::Gitignore; use krates::NodeId; use notify::{ event::{MetadataKind, ModifyKind}, Config, EventKind, RecursiveMode, Watcher as NotifyWatcher, }; +use std::time::SystemTime; use std::{ collections::{HashMap, HashSet}, - net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}, + net::{IpAddr, TcpListener}, path::PathBuf, - process::Stdio, - str::FromStr, sync::Arc, time::Duration, }; -use std::{path::Path, time::SystemTime}; use subsecond_types::JumpTable; use syn::spanned::Spanned; -use target_lexicon::Triple; use tokio::process::Command; /// This is the primary "state" object that holds the builds and handles for the running apps. @@ -54,7 +49,7 @@ pub(crate) struct AppServer { // Related to to the filesystem watcher pub(crate) watcher: Box, - pub(crate) watcher_tx: UnboundedSender, + pub(crate) _watcher_tx: UnboundedSender, pub(crate) watcher_rx: UnboundedReceiver, // Tracked state related to open builds and hot reloading @@ -65,10 +60,10 @@ pub(crate) struct AppServer { pub(crate) use_hotpatch_engine: bool, pub(crate) automatic_rebuilds: bool, pub(crate) interactive: bool, - pub(crate) force_sequential: bool, + pub(crate) _force_sequential: bool, pub(crate) hot_reload: bool, pub(crate) open_browser: bool, - pub(crate) wsl_file_poll_interval: u16, + pub(crate) _wsl_file_poll_interval: u16, pub(crate) always_on_top: bool, pub(crate) fullstack: bool, pub(crate) watch_fs: bool, @@ -238,7 +233,7 @@ impl AppServer { server, hot_reload, open_browser, - wsl_file_poll_interval, + _wsl_file_poll_interval: wsl_file_poll_interval, always_on_top, workspace, devserver_port, @@ -246,9 +241,9 @@ impl AppServer { proxied_port, watcher, watcher_rx, - watcher_tx, + _watcher_tx: watcher_tx, interactive, - force_sequential, + _force_sequential: force_sequential, cross_origin_policy, fullstack, }; @@ -611,6 +606,124 @@ impl AppServer { } } + /// Perform a full rebuild of the app, equivalent to `cargo rustc` from scratch with no incremental + /// hot-patch engine integration. + pub(crate) async fn full_rebuild(&mut self) { + let build_mode = match self.use_hotpatch_engine { + true => BuildMode::Fat, + false => BuildMode::Base, + }; + + self.client.start_rebuild(build_mode.clone()); + self.server.as_mut().map(|s| s.start_rebuild(build_mode)); + + self.clear_hot_reload_changes(); + self.clear_cached_rsx(); + } + + pub(crate) async fn patch(&mut self, res: &BuildArtifacts) -> Result { + let client = match res.platform { + Platform::Server => &self.server.as_ref().unwrap(), + _ => &self.client, + }; + + let original = client.build.main_exe(); + let new = client.build.patch_exe(res.time_start); + let triple = client.build.triple.clone(); + + tracing::debug!("Patching {} -> {}", original.display(), new.display()); + + let mut jump_table = crate::build::create_jump_table(&original, &new, &triple).unwrap(); + + // If it's android, we need to copy the assets to the device and then change the location of the patch + if client.build.platform == Platform::Android { + jump_table.lib = client + .copy_file_to_android_tmp(&new, &(PathBuf::from(new.file_name().unwrap()))) + .await?; + } + + // Rebase the wasm binary to be relocatable once the jump table is generated + if triple.architecture == target_lexicon::Architecture::Wasm32 { + // Make sure we use the dir relative to the public dir, so the web can load it as a proper URL + // + // ie we would've shipped `/Users/foo/Projects/dioxus/target/dx/project/debug/web/public/wasm/lib.wasm` + // but we want to ship `/wasm/lib.wasm` + jump_table.lib = jump_table + .lib + .strip_prefix(&client.build.root_dir()) + .unwrap() + .to_path_buf(); + } + + let changed_files = match &res.mode { + BuildMode::Thin { changed_files, .. } => changed_files.clone(), + _ => vec![], + }; + + let changed_file = changed_files.first().unwrap(); + tracing::info!( + "Hot-patching: {} took {:?}ms", + changed_file + .strip_prefix(std::env::current_dir().unwrap()) + .unwrap_or_else(|_| changed_file.as_path()) + .display(), + SystemTime::now() + .duration_since(res.time_start) + .unwrap() + .as_millis() + ); + + // Save this patch + self.client.patches.push(jump_table.clone()); + + Ok(jump_table) + } + + /// Handles incoming WebSocket messages from the client. + /// + /// This function processes messages sent by the client over the WebSocket connection. We only + /// handle text messages, and we expect them to be in JSON format. + /// + /// Specifically, it handles the initialization message to set the Address Space Layout Randomization (ASLR) reference offset. + /// + /// For WebAssembly (Wasm) targets, ASLR is not used, so this value is ignored. + pub(crate) async fn handle_ws_message(&mut self, msg: &WsMessage) -> Result<()> { + let as_text = msg + .to_text() + .context("client message not proper encoding")?; + + if as_text.is_empty() { + return Ok(()); + } + + match serde_json::from_str::(as_text) { + Ok(ClientMsg::Initialize { + aslr_reference, + build_id, + }) => { + tracing::debug!( + "Setting aslr_reference: {aslr_reference} for build_id: {build_id}" + ); + match build_id { + 0 => { + self.client.aslr_reference = Some(aslr_reference); + } + _ => { + if let Some(server) = self.server.as_mut() { + server.aslr_reference = Some(aslr_reference); + } + } + } + } + Ok(_client) => {} + Err(err) => { + tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {} -> {}", Platform::Web, err, as_text); + } + }; + + Ok(()) + } + pub(crate) fn get_build(&self, id: BuildId) -> Option<&AppBuilder> { match id { BuildId::CLIENT => Some(&self.client), @@ -729,7 +842,6 @@ impl AppServer { let path = entry.path(); if path.extension().and_then(|s| s.to_str()) == Some("rs") { if let Ok(contents) = std::fs::read_to_string(&path) { - // if let Ok(path) = path.strip_prefix(self.workspace.workspace_dir()) { self.file_map.insert( path.to_path_buf(), CachedFile { @@ -738,7 +850,6 @@ impl AppServer { templates: Default::default(), }, ); - // } } } } @@ -749,7 +860,7 @@ impl AppServer { /// Removes any cached templates and replaces the contents of the files with the most recent /// /// todo: we should-reparse the contents so we never send a new version, ever - pub(crate) fn clear_cached_rsx(&mut self) { + fn clear_cached_rsx(&mut self) { for cached_file in self.file_map.values_mut() { if let Some(most_recent) = cached_file.most_recent.take() { cached_file.contents = most_recent; @@ -758,109 +869,6 @@ impl AppServer { } } - pub(crate) async fn patch(&mut self, res: &BuildArtifacts) -> Result { - let client = match res.platform { - Platform::Server => &self.server.as_ref().unwrap(), - _ => &self.client, - }; - - let original = client.build.main_exe(); - let new = client.build.patch_exe(res.time_start); - let triple = client.build.triple.clone(); - - tracing::debug!("Patching {} -> {}", original.display(), new.display()); - - let mut jump_table = crate::build::create_jump_table(&original, &new, &triple).unwrap(); - - // If it's android, we need to copy the assets to the device and then change the location of the patch - if client.build.platform == Platform::Android { - jump_table.lib = client - .copy_file_to_android_tmp(&new, &(PathBuf::from(new.file_name().unwrap()))) - .await?; - } - - // Rebase the wasm binary to be relocatable once the jump table is generated - if triple.architecture == target_lexicon::Architecture::Wasm32 { - // Make sure we use the dir relative to the public dir, so the web can load it as a proper URL - // - // ie we would've shipped `/Users/foo/Projects/dioxus/target/dx/project/debug/web/public/wasm/lib.wasm` - // but we want to ship `/wasm/lib.wasm` - jump_table.lib = jump_table - .lib - .strip_prefix(&client.build.root_dir()) - .unwrap() - .to_path_buf(); - } - - let changed_files = match &res.mode { - BuildMode::Thin { changed_files, .. } => changed_files.clone(), - _ => vec![], - }; - - let changed_file = changed_files.first().unwrap(); - tracing::info!( - "Hot-patching: {} took {:?}ms", - changed_file - .strip_prefix(std::env::current_dir().unwrap()) - .unwrap_or_else(|_| changed_file.as_path()) - .display(), - SystemTime::now() - .duration_since(res.time_start) - .unwrap() - .as_millis() - ); - - // Save this patch - self.client.patches.push(jump_table.clone()); - - Ok(jump_table) - } - - /// Handles incoming WebSocket messages from the client. - /// - /// This function processes messages sent by the client over the WebSocket connection. We only - /// handle text messages, and we expect them to be in JSON format. - /// - /// Specifically, it handles the initialization message to set the Address Space Layout Randomization (ASLR) reference offset. - /// - /// For WebAssembly (Wasm) targets, ASLR is not used, so this value is ignored. - pub(crate) async fn handle_ws_message(&mut self, msg: &WsMessage) -> Result<()> { - let as_text = msg - .to_text() - .context("client message not proper encoding")?; - - if as_text.is_empty() { - return Ok(()); - } - - match serde_json::from_str::(as_text) { - Ok(ClientMsg::Initialize { - aslr_reference, - build_id, - }) => { - tracing::debug!( - "Setting aslr_reference: {aslr_reference} for build_id: {build_id}" - ); - match build_id { - 0 => { - self.client.aslr_reference = Some(aslr_reference); - } - _ => { - if let Some(server) = self.server.as_mut() { - server.aslr_reference = Some(aslr_reference); - } - } - } - } - Ok(_client) => {} - Err(err) => { - tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {} -> {}", Platform::Web, err, as_text); - } - }; - - Ok(()) - } - fn watch_filesystem(&mut self) { // Watch the folders of the crates that we're interested in for path in self.watch_paths( @@ -892,7 +900,7 @@ impl AppServer { } /// Return the list of paths that we should watch for changes. - pub(crate) fn watch_paths(&self, crate_dir: PathBuf, crate_package: NodeId) -> Vec { + fn watch_paths(&self, crate_dir: PathBuf, crate_package: NodeId) -> Vec { let mut watched_paths = vec![]; // Get a list of *all* the crates with Rust code that we need to watch. @@ -936,7 +944,7 @@ impl AppServer { /// - the assets directory - this is so we can hotreload CSS and other assets by default /// - the Cargo.toml file - this is so we can hotreload the project if the user changes dependencies /// - the Dioxus.toml file - this is so we can hotreload the project if the user changes the Dioxus config - pub(crate) fn local_dependencies(&self, crate_package: NodeId) -> Vec { + fn local_dependencies(&self, crate_package: NodeId) -> Vec { let mut paths = vec![]; for (dependency, _edge) in self.workspace.krates.get_deps(crate_package) { @@ -1002,21 +1010,6 @@ impl AppServer { server.compiled_crates as f64 / server.expected_crates as f64 } - - /// Perform a full rebuild of the app, equivalent to `cargo rustc` from scratch with no incremental - /// hot-patch engine integration. - pub(crate) async fn full_rebuild(&mut self) { - let build_mode = match self.use_hotpatch_engine { - true => BuildMode::Fat, - false => BuildMode::Base, - }; - - self.client.start_rebuild(build_mode.clone()); - self.server.as_mut().map(|s| s.start_rebuild(build_mode)); - - self.clear_hot_reload_changes(); - self.clear_cached_rsx(); - } } /// Bind a listener to any point and return it diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index fa771d70d6..1c7f41f269 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -199,7 +199,7 @@ use std::{ backtrace, mem::transmute, - panic::{panic_any, AssertUnwindSafe, UnwindSafe}, + panic::AssertUnwindSafe, sync::{Arc, Mutex}, }; @@ -339,9 +339,7 @@ impl> HotFn { } pub fn register_handler(handler: Arc) { - unsafe { - HOTRELOAD_HANDLERS.lock().unwrap().push(handler); - } + HOTRELOAD_HANDLERS.lock().unwrap().push(handler); } /// Apply the patch using a given jump table. @@ -750,17 +748,17 @@ macro_rules! impl_hot_function { // If we leave the tag, then indexing our jump table will fail and patching won't work (or crash!) // This is only implemented on 64-bit platforms since pointer tagging is not available on 32-bit platforms // In dev, Dioxus disables MTE to work around this issue, but we still handle it anyways. - #[cfg(target_pointer_width = "64")] let nibble = real as u64 & 0xFF00_0000_0000_0000; + #[cfg(all(target_pointer_width = "64", target_os = "android"))] let nibble = real as u64 & 0xFF00_0000_0000_0000; #[cfg(target_pointer_width = "64")] let real = real as u64 & 0x00FFF_FFF_FFFF_FFFF; #[cfg(target_pointer_width = "64")] let real = real as u64; - // No nibble on 32-bit platforms, but we still need to assume u64 since the host always writes 64-bit pointers + // No nibble on 32-bit platforms, but we still need to assume u64 since the host always writes 64-bit addresses #[cfg(target_pointer_width = "32")] let real = real as u64; if let Some(ptr) = jump_table.map.get(&real).cloned() { // Re-apply the nibble - though this might not be required (we aren't calling malloc for a new pointer) - // #[cfg(target_pointer_width = "64")] let ptr: u64 = ptr | nibble; + #[cfg(all(target_pointer_width = "64", target_os = "android"))] let ptr: u64 = ptr | nibble; #[cfg(target_pointer_width = "64")] let ptr: u64 = ptr; #[cfg(target_pointer_width = "32")] let ptr: u32 = ptr as u32; From ebcb12d57a169bd7dc44dcec849ff1ff2017df3d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 22 Apr 2025 12:08:38 -0700 Subject: [PATCH 169/301] fix serverfn feature sets --- Cargo.lock | 28 ++--- Cargo.toml | 2 + packages/cli/src/build/patch.rs | 9 -- packages/cli/src/build/request.rs | 7 ++ packages/core/src/nodes.rs | 4 +- packages/devtools/Cargo.toml | 1 + packages/devtools/src/lib.rs | 8 +- packages/dioxus/Cargo.toml | 7 +- packages/fullstack/Cargo.toml | 35 ++++--- packages/liveview/src/pool.rs | 5 +- packages/native/src/dioxus_application.rs | 6 +- packages/server-macro/Cargo.toml | 4 +- packages/server/Cargo.toml | 44 +++++--- packages/server/src/launch.rs | 120 ++++++++++------------ packages/server/src/lib.rs | 32 +----- packages/server/src/router.rs | 65 +----------- packages/server/src/rt.rs | 2 +- packages/server/src/server_context.rs | 3 - packages/subsecond/subsecond/src/lib.rs | 3 +- packages/web/src/devtools.rs | 14 +-- 20 files changed, 161 insertions(+), 238 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a560763fd0..05ba12c1db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2620,9 +2620,9 @@ dependencies = [ [[package]] name = "const-str" -version = "0.5.7" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" +checksum = "9e991226a70654b49d34de5ed064885f0bef0348a8e70018b8ff1ac80aa984a2" [[package]] name = "const-str-proc-macro" @@ -3938,6 +3938,7 @@ dependencies = [ "serde", "serde_json", "subsecond", + "thiserror 2.0.12", "tokio", "tracing", "tungstenite 0.26.2", @@ -4448,7 +4449,6 @@ dependencies = [ "dioxus-lib", "dioxus-router", "dioxus-ssr", - "dioxus_server_macro", "futures-channel", "futures-util", "generational-box", @@ -12151,13 +12151,13 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" +checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" dependencies = [ "percent-encoding", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] @@ -12237,14 +12237,14 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.8.0-rc1" +version = "0.8.0-rc2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b279618eba9bf2298336a6b5f72c84bee46a5a76ad048a5763b552d69393988" +checksum = "382f52442c967ec604c643250dd64973036be257872b928127b444db4c7ff90c" dependencies = [ "axum 0.8.3", "base64 0.22.1", "bytes", - "const-str 0.5.7", + "const-str 0.6.2", "const_format", "dashmap 6.1.0", "futures", @@ -12279,12 +12279,12 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.8.0-rc1" +version = "0.8.0-rc2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c805d4055915c94d1b168bec37afc9fadcfd7c17798c63259e303667076a67" +checksum = "e2b25202696036a77664e49420e189a96e5af3a8583b5b770debdc8c1dc73ac5" dependencies = [ "const_format", - "convert_case 0.6.0", + "convert_case 0.8.0", "proc-macro2", "quote", "rustc_version 0.4.1", @@ -12294,9 +12294,9 @@ dependencies = [ [[package]] name = "server_fn_macro_default" -version = "0.8.0-rc1" +version = "0.8.0-rc2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63085deb77b447034fd865a7a054c0a234bd1ce93c9cf13dd02e0138773930f" +checksum = "4753763e785a383bf0630d6e8694610545bb194c94d3c042c95fa6fc7be70dfb" dependencies = [ "server_fn_macro", "syn 2.0.100", diff --git a/Cargo.toml b/Cargo.toml index 5fa706141b..9c5a9e487e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,6 +225,8 @@ lru = "0.13.0" async-trait = "0.1.87" axum = "0.8.1" axum-server = { version = "0.7.1", default-features = false } +server_fn = { version = "0.8.0-rc2", default-features = false } +server_fn_macro = { version = "0.8.0-rc2" } tower = "0.5.2" http = "1.2.0" notify = { version = "8.0.0" } diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 4c8fd6bd7a..a4cfeaaeea 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -768,15 +768,6 @@ fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result { }) } -struct SymbolMap<'a> { - symbols: Vec>, -} - -enum Node { - Function(FunctionId), - Data(usize), -} - async fn attempt_partial_link(proc_main_addr: u64, patch_target: PathBuf, out_path: PathBuf) { let mut object = ObjectDiff::new().unwrap(); object.load().unwrap(); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 029c909ccd..a1ce7ff4d7 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1933,6 +1933,13 @@ impl BuildRequest { } } + tracing::debug!( + "cargo args for {} - {}: {:#?}", + self.platform, + self.triple, + cargo_args + ); + cargo_args } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 7f496d4a5b..8131899c6d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -383,9 +383,7 @@ fn static_items_merged() -> bool { fn a() {} fn b() {} - // a as fn() == b as fn() - // false - true + a as fn() == b as fn() } impl std::hash::Hash for Template { diff --git a/packages/devtools/Cargo.toml b/packages/devtools/Cargo.toml index 17ee44e8fc..54d6309c03 100644 --- a/packages/devtools/Cargo.toml +++ b/packages/devtools/Cargo.toml @@ -19,6 +19,7 @@ serde_json = { workspace = true } subsecond = { workspace = true } libloading = "0.8.3" libc = "0.2.155" +thiserror = { workspace = true } # hot reloading serve tracing = { workspace = true } diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index 1767013e41..0e6051a3fe 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -5,10 +5,16 @@ use warnings::Warning; pub use dioxus_devtools_types::*; pub use subsecond; +#[derive(Debug, Clone, PartialEq, thiserror::Error)] +pub enum DevtoolsError { + #[error("Failed to load the patch: {0}")] + PatchFailed(#[from] subsecond::PatchError), +} + /// Applies template and literal changes to the VirtualDom /// /// Assets need to be handled by the renderer. -pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), subsecond::PatchError> { +pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), DevtoolsError> { dom.runtime().on_scope(ScopeId::ROOT, || { // 1. Update signals... let ctx = dioxus_signals::get_global_context(); diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 669924b6c3..e508124b8d 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -95,11 +95,12 @@ server = [ "dep:dioxus-server", "dep:dioxus_server_macro", "dioxus_server_macro/server", + "dioxus_server_macro/axum", "ssr", "dioxus-liveview?/axum", - "dioxus-fullstack/server", - "dep:dioxus-fullstack", - "fullstack" + "dioxus-fullstack?/server", + # "dep:dioxus-fullstack", + # "fullstack" ] # This feature just disables the no-renderer-enabled warning diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index 4a429569e1..b23d1435cb 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" [dependencies] # server functions -server_fn = { version = "0.8.0-rc1", features = ["browser"], default-features = false } +server_fn = { workspace = true, features = ["browser"] } dioxus_server_macro = { workspace = true } # axum @@ -92,23 +92,26 @@ devtools = ["dioxus-web?/devtools", "dep:dioxus-devtools"] mounted = ["dioxus-web?/mounted"] file_engine = ["dioxus-web?/file_engine"] document = ["dioxus-web?/document"] -web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web"] -desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] -mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web", "server_fn/browser"] +# web = ["dep:dioxus-web", "dep:web-sys", "dioxus-fullstack-hooks/web", "server_fn/browser", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +desktop = [] +mobile = [] +# desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +# mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] default-tls = ["server_fn/default-tls"] rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"] -axum_core = [ - "dep:axum", - "server_fn/axum-no-default", - "dioxus_server_macro/axum", - "default-tls", - "server", -] -axum = [ - "dep:tower-http", - "server_fn/axum", - "axum_core", -] +# axum_core = [ +# "dep:axum", +# "server_fn/axum-no-default", +# "dioxus_server_macro/axum", +# "default-tls", +# "server", +# ] +# axum = [ +# "dep:tower-http", +# "server_fn/axum", +# "axum_core", +# ] server = [ "server_fn/ssr", "dioxus_server_macro/server", diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index add0910b1a..e9cddf14bc 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -217,7 +217,10 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li #[cfg(all(feature = "devtools", debug_assertions))] match msg { dioxus_devtools::DevserverMsg::HotReload(msg)=> { - dioxus_devtools::apply_changes(&vdom, &msg); + if dioxus_devtools::apply_changes(&vdom, &msg).is_err() + { + tracing::error!("Failed to hot patch! App might be unstable now."); + } } dioxus_devtools::DevserverMsg::Shutdown => { std::process::exit(0); diff --git a/packages/native/src/dioxus_application.rs b/packages/native/src/dioxus_application.rs index 3c65cc6836..bee852e5b0 100644 --- a/packages/native/src/dioxus_application.rs +++ b/packages/native/src/dioxus_application.rs @@ -48,7 +48,11 @@ impl DioxusNativeApplication { DioxusNativeEvent::DevserverEvent(event) => match event { dioxus_devtools::DevserverMsg::HotReload(hotreload_message) => { for window in self.inner.windows.values_mut() { - dioxus_devtools::apply_changes(&window.doc.vdom, hotreload_message); + if dioxus_devtools::apply_changes(&window.doc.vdom, hotreload_message) + .is_err() + { + tracing::error!("Failed to hot patch! App might be unstable now."); + } window.poll(); } } diff --git a/packages/server-macro/Cargo.toml b/packages/server-macro/Cargo.toml index cc4ca8057f..2180c0079a 100644 --- a/packages/server-macro/Cargo.toml +++ b/packages/server-macro/Cargo.toml @@ -13,7 +13,8 @@ description = "Server function macros for Dioxus" proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true, features = ["full"] } -server_fn_macro = "0.8.0-rc1" +server_fn_macro = { workspace = true } + [dev-dependencies] dioxus = { workspace = true, features = ["fullstack"] } @@ -25,6 +26,7 @@ axum = { workspace = true } proc-macro = true [features] +default = [] axum = ["server_fn_macro/axum"] server = ["server_fn_macro/ssr"] reqwest = ["server_fn_macro/reqwest"] diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 288a5404c1..76f4b355dc 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -12,13 +12,11 @@ resolver = "2" [dependencies] # server functions -server_fn = { version = "0.8.0-rc1", features = ["browser"], default-features = false } -dioxus_server_macro = { workspace = true } +server_fn = { workspace = true, default-features = false } # axum axum = { workspace = true } -tower-http = { workspace = true, features = ["fs"], optional = true} - +tower-http = { workspace = true, features = ["fs"], optional = true } dioxus-lib = { workspace = true } generational-box = { workspace = true } @@ -28,7 +26,7 @@ dioxus-isrg = { workspace = true } dioxus-router = { workspace = true, features = ["streaming"] } dioxus-fullstack-hooks = { workspace = true } dioxus-fullstack-protocol = { workspace = true } -dioxus-interpreter-js = { workspace = true, optional = true} +dioxus-interpreter-js = { workspace = true, optional = true } hyper = { workspace = true } http = { workspace = true } @@ -44,7 +42,7 @@ futures-util = { workspace = true } futures-channel = { workspace = true } ciborium = { workspace = true } base64 = { workspace = true } -rustls = { workspace = true, optional = true} +rustls = { workspace = true, optional = true } hyper-rustls = { workspace = true, optional = true } pin-project = { version = "1.1.2" } @@ -53,16 +51,22 @@ bytes = "1.4.0" tower = { workspace = true, features = ["util"] } tower-layer = { version = "0.3.2" } parking_lot = { workspace = true, features = ["send_guard"] } -web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] } +web-sys = { version = "0.3.61", features = [ + "Window", + "Document", + "Element", + "HtmlDocument", + "Storage", + "console", +] } dioxus-cli-config = { workspace = true } - -dioxus-devtools = { workspace = true, optional = true} -aws-lc-rs = { version = "1.8.1", optional = true} +dioxus-devtools = { workspace = true, optional = true } +aws-lc-rs = { version = "1.8.1", optional = true } dioxus-history = { workspace = true } hyper-util = { workspace = true, features = ["full"] } subsecond.workspace = true -inventory = { workspace = true } +inventory = { workspace = true } dashmap = "6.1.0" [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -75,14 +79,23 @@ tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"] } dioxus = { workspace = true, features = ["fullstack"] } [features] -default = ["devtools", "document", "server_fn/axum", "dioxus-fullstack-hooks/server", "server_fn/ssr", "dep:tower-http"] -# default = ["devtools", "document", "file_engine", "mounted"] +default = [ + "devtools", + "document", + "dioxus-fullstack-hooks/server", + "server_fn/axum", + "server_fn/ssr", + "dep:tower-http", +] devtools = ["dep:dioxus-devtools"] document = ["dep:dioxus-interpreter-js"] -# desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] -# mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] default-tls = ["server_fn/default-tls"] rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"] +aws-lc-rs = ["dep:aws-lc-rs"] + +# default = ["devtools", "document", "file_engine", "mounted"] +# desktop = ["dep:dioxus-desktop", "server_fn/reqwest", "dioxus_server_macro/reqwest"] +# mobile = ["dep:dioxus-mobile", "server_fn/reqwest", "dioxus_server_macro/reqwest"] # document = ["d"] # axum_core = [ # "dep:axum", @@ -118,7 +131,6 @@ rustls = ["server_fn/rustls", "dep:rustls", "dep:hyper-rustls"] # "dep:parking_lot", # "dioxus-interpreter-js", # ] -aws-lc-rs = ["dep:aws-lc-rs"] [package.metadata.docs.rs] cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index af0640e07b..cb1db7bad1 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -1,36 +1,30 @@ //! A launch function that creates an axum router for the LaunchBuilder -use std::{any::Any, collections::HashMap, net::SocketAddr}; - +use crate::{ + collect_raw_server_fns, render_handler, rt::DioxusRouterExt, RenderHandleState, SSRState, + ServeConfig, ServeConfigBuilder, +}; use axum::{ body::Body, extract::{Request, State}, response::IntoResponse, routing::IntoMakeService, - serve::IncomingStream, }; -use dashmap::DashMap; use dioxus_cli_config::base_path; use dioxus_devtools::DevserverMsg; use dioxus_lib::prelude::*; -use futures_util::{pin_mut, stream::FusedStream, StreamExt}; +use futures_util::{stream::FusedStream, StreamExt}; use hyper::body::Incoming; use hyper_util::server::conn::auto::Builder as HyperBuilder; use hyper_util::{ rt::{TokioExecutor, TokioIo}, service::TowerToHyperService, }; -use server_fn::ServerFnTraitObj; +use std::{any::Any, collections::HashMap, net::SocketAddr}; use tokio::net::TcpStream; use tokio_util::task::LocalPoolHandle; use tower::Service; use tower::ServiceExt as _; -// use tower::{Service, ServiceExt}; - -use crate::{ - collect_raw_server_fns, register_server_fn_on_router, render_handler, rt::DioxusRouterExt, - RenderHandleState, SSRState, ServeConfig, ServeConfigBuilder, -}; type ContextList = Vec Box + Send + Sync>>; @@ -95,7 +89,13 @@ async fn serve_server( // and we use the generated address the CLI gives us let address = dioxus_cli_config::fullstack_address_or_localhost(); - let router = axum::Router::new().serve_dioxus_application(cfg.clone(), root); + // Create the router and register the server functions under the basepath. + let router = apply_base_path( + axum::Router::new().serve_dioxus_application(cfg.clone(), root), + root, + cfg.clone(), + base_path().map(|s| s.to_string()), + ); let task_pool = LocalPoolHandle::new(5); let mut make_service = router.into_make_service(); @@ -138,9 +138,6 @@ async fn serve_server( match devserver_msg { DevserverMsg::HotReload(hot_reload_msg) => { if let Some(table) = hot_reload_msg.jump_table { - use axum::body::Body; - use http::{Method, Request, Response, StatusCode}; - if let Ok(_) = unsafe { dioxus_devtools::subsecond::apply_patch(table) } { let mut new_router = axum::Router::new().serve_static_assets(); @@ -166,18 +163,20 @@ async fn serve_server( ); } - let ssr_state = SSRState::new(&cfg); + let state = RenderHandleState::new(cfg.clone(), root) + .with_ssr_state(SSRState::new(&cfg)); - make_service = new_router - .fallback( - axum::routing::get(render_handler).with_state( - RenderHandleState::new(cfg.clone(), root) - .with_ssr_state(ssr_state), - ), - ) - .into_make_service(); + let fallback_handler = + axum::routing::get(render_handler).with_state(state); + + make_service = apply_base_path( + new_router.fallback(fallback_handler), + root, + cfg.clone(), + base_path().map(|s| s.to_string()), + ) + .into_make_service(); - tracing::info!("Shutting down connections..."); _ = shutdown_tx.send_modify(|i| { *i += 1; hr_idx += 1; @@ -198,7 +197,7 @@ async fn serve_server( let mut make_service = make_service.clone(); let mut shutdown_rx = shutdown_rx.clone(); - let mut hr_idx = hr_idx.clone(); + let this_hr_index = hr_idx.clone(); task_pool.spawn_pinned(move || async move { let tcp_stream = TokioIo::new(tcp_stream); @@ -226,7 +225,7 @@ async fn serve_server( tokio::select! { res = connection => { - if let Err(err) = res { + if let Err(_err) = res { // This error only appears when the client doesn't send a request and // terminate the connection. // @@ -234,7 +233,7 @@ async fn serve_server( // appear. } } - res = shutdown_rx.wait_for(|i| *i == hr_idx + 1) => { + res = shutdown_rx.wait_for(|i| *i == this_hr_index + 1) => { tracing::info!("Shutting down connection server: {res:?}"); return; } @@ -245,43 +244,34 @@ async fn serve_server( } } -fn build_router( +fn apply_base_path( + mut router: axum::Router, root: fn() -> Result, - platform_config: Result, + cfg: ServeConfig, + base_path: Option, ) -> axum::Router { - let mut base_path = base_path(); - - let dioxus_router = - axum::Router::new().serve_dioxus_application(platform_config.unwrap(), root); - - let router = dioxus_router; - - // let mut router; - // match base_path.as_deref() { - // Some(base_path) => { - // let base_path = base_path.trim_matches('/'); - // // If there is a base path, nest the router under it and serve the root route manually - // // Nesting a route in axum only serves /base_path or /base_path/ not both - // router = axum::Router::new().nest(&format!("/{base_path}/"), dioxus_router); - // async fn root_render_handler( - // state: State, - // mut request: Request, - // ) -> impl IntoResponse { - // // The root of the base path always looks like the root from dioxus fullstack - // *request.uri_mut() = "/".parse().unwrap(); - // render_handler(state, request).await - // } - // if let Some(cfg) = config { - // let ssr_state = SSRState::new(&cfg); - // router = router.route( - // &format!("/{base_path}"), - // axum::routing::method_routing::get(root_render_handler).with_state( - // RenderHandleState::new(cfg, root).with_ssr_state(ssr_state), - // ), - // ) - // } - // } - // None => router = dioxus_router, - // } + if let Some(base_path) = base_path { + let base_path = base_path.trim_matches('/'); + // If there is a base path, nest the router under it and serve the root route manually + // Nesting a route in axum only serves /base_path or /base_path/ not both + router = axum::Router::new().nest(&format!("/{base_path}/"), router); + + async fn root_render_handler( + state: State, + mut request: Request, + ) -> impl IntoResponse { + // The root of the base path always looks like the root from dioxus fullstack + *request.uri_mut() = "/".parse().unwrap(); + render_handler(state, request).await + } + + let ssr_state = SSRState::new(&cfg); + router = router.route( + &format!("/{base_path}"), + axum::routing::method_routing::get(root_render_handler) + .with_state(RenderHandleState::new(cfg, root).with_ssr_state(ssr_state)), + ) + } + router } diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index e6e220992e..cedd8117c5 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -64,36 +64,14 @@ mod serve_config; mod server_context; mod streaming; -pub(crate) use document::*; -pub(crate) use launch::*; -pub(crate) use render::*; -pub(crate) use router::*; -pub(crate) use rt::*; pub(crate) use serve_config::*; -pub(crate) use server_context::*; -pub(crate) use streaming::*; - -pub use launch::launch; - -// pub mod document; - -// #[cfg(feature = "axum")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -// pub use crate::server::*; - -// #[cfg(feature = "server")] -// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] -// pub use crate::render::{FullstackHTMLTemplate, SSRState}; - -// #[cfg(feature = "server")] -// #[cfg_attr(docsrs, doc(cfg(feature = "server")))] -// pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; - -// #[cfg(feature = "axum")] -// #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -// pub use crate::server_context::Axum; +pub use crate::render::{FullstackHTMLTemplate, SSRState}; +pub use crate::rt::*; +pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; +pub use crate::server_context::Axum; pub use document::ServerDocument; +pub use launch::launch; pub use serve_config::*; pub use server_context::{ extract, server_context, with_server_context, DioxusServerContext, FromContext, diff --git a/packages/server/src/router.rs b/packages/server/src/router.rs index efc1cba7c9..8b13789179 100644 --- a/packages/server/src/router.rs +++ b/packages/server/src/router.rs @@ -1,64 +1 @@ -//! Dioxus core utilities for the [Axum](https://docs.rs/axum/latest/axum/index.html) server framework. -//! -//! # Example -//! ```rust, no_run -//! #![allow(non_snake_case)] -//! use dioxus::prelude::*; -//! -//! fn main() { -//! #[cfg(feature = "web")] -//! // Hydrate the application on the client -//! dioxus::launch(app); -//! #[cfg(feature = "server")] -//! { -//! tokio::runtime::Runtime::new() -//! .unwrap() -//! .block_on(async move { -//! // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address -//! // and we use the generated address the CLI gives us -//! let address = dioxus::cli_config::fullstack_address_or_localhost(); -//! let listener = tokio::net::TcpListener::bind(address) -//! .await -//! .unwrap(); -//! axum::serve( -//! listener, -//! axum::Router::new() -//! // Server side render the application, serve static assets, and register server functions -//! .register_server_functions() -//! .fallback(get(render_handler) -//! // Note: ServeConfig::new won't work on WASM -//! .with_state(RenderHandler::new(ServeConfig::new().unwrap(), app)) -//! ) -//! .into_make_service(), -//! ) -//! .await -//! .unwrap(); -//! }); -//! } -//! } -//! -//! fn app() -> Element { -//! let mut text = use_signal(|| "...".to_string()); -//! -//! rsx! { -//! button { -//! onclick: move |_| async move { -//! if let Ok(data) = get_server_data().await { -//! text.set(data); -//! } -//! }, -//! "Run a server function" -//! } -//! "Server said: {text}" -//! } -//! } -//! -//! #[server(GetServerData)] -//! async fn get_server_data() -> Result { -//! Ok("Hello from the server!".to_string()) -//! } -//! -//! # WASM support -//! -//! These utilities compile to the WASM family of targets, while the more complete ones found in [server] don't -//! ``` + diff --git a/packages/server/src/rt.rs b/packages/server/src/rt.rs index 5ad15b4bc8..94ce51a24a 100644 --- a/packages/server/src/rt.rs +++ b/packages/server/src/rt.rs @@ -10,7 +10,7 @@ use axum::{ }; use dioxus_lib::prelude::{Element, VirtualDom}; use http::header::*; -use server_fn::{middleware::BoxedService, ServerFnTraitObj}; +use server_fn::ServerFnTraitObj; use std::sync::Arc; /// A extension trait with utilities for integrating Dioxus with your Axum router. diff --git a/packages/server/src/server_context.rs b/packages/server/src/server_context.rs index 93689d3dbb..acfca82eee 100644 --- a/packages/server/src/server_context.rs +++ b/packages/server/src/server_context.rs @@ -426,12 +426,9 @@ impl FromServerContext for FromContext { } } -#[cfg(feature = "axum")] -#[cfg_attr(docsrs, doc(cfg(feature = "axum")))] /// An adapter for axum extractors for the server context pub struct Axum; -#[cfg(feature = "axum")] #[async_trait::async_trait] impl> FromServerContext for I { type Rejection = I::Rejection; diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs index 1c7f41f269..7fcd1ac9ce 100644 --- a/packages/subsecond/subsecond/src/lib.rs +++ b/packages/subsecond/subsecond/src/lib.rs @@ -532,8 +532,9 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> { Ok(()) } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum PatchError { + #[error("Failed to load the patch")] CantLoadPatch, } diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index 74ee9d2849..bef67a75f7 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -163,7 +163,7 @@ fn make_ws(tx: UnboundedSender, poll_interval: i32, reload: bool) if reload { window().unwrap().location().reload().unwrap(); } else { - ws_tx.send_with_str( + _ = ws_tx.send_with_str( &serde_json::to_string(&ClientMsg::Initialize { build_id: dioxus_cli_config::build_id(), aslr_reference: dioxus_devtools::subsecond::aslr_reference() as _, @@ -209,16 +209,6 @@ impl Display for ToastLevel { } } -pub(crate) fn close_toast() { - js_sys::eval( - r#" - if (typeof closeDXToast !== "undefined") { - window.closeDXToast(); - } - "#, - ); -} - /// Displays a toast to the developer. pub(crate) fn show_toast( header_text: &str, @@ -234,7 +224,7 @@ pub(crate) fn show_toast( false => "showDXToast", }; - js_sys::eval(&format!( + _ = js_sys::eval(&format!( r#" if (typeof {js_fn_name} !== "undefined") {{ window.{js_fn_name}("{header_text}", "{message}", "{level}", {as_ms}); From e7ce1623f153e503fcb9a03e87dc1ce50cf91cb7 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 22 Apr 2025 12:57:44 -0700 Subject: [PATCH 170/301] remove toast animation and simplify its code a bit --- packages/cli/assets/web/dev.index.html | 108 ++++++++++--------------- packages/cli/src/build/patch.rs | 1 - packages/cli/src/build/request.rs | 44 +++------- packages/cli/src/serve/mod.rs | 2 +- packages/cli/src/serve/runner.rs | 4 +- packages/cli/src/serve/server.rs | 15 +++- packages/depinfo/README.md | 12 ++- packages/desktop/src/app.rs | 12 +-- packages/desktop/src/launch.rs | 4 +- packages/devtools-types/src/lib.rs | 1 + packages/dioxus/Cargo.toml | 2 - packages/fullstack/src/web.rs | 15 ++-- packages/web/src/lib.rs | 16 ++-- 13 files changed, 106 insertions(+), 130 deletions(-) diff --git a/packages/cli/assets/web/dev.index.html b/packages/cli/assets/web/dev.index.html index e2a9ee9662..7c695b512a 100644 --- a/packages/cli/assets/web/dev.index.html +++ b/packages/cli/assets/web/dev.index.html @@ -5,32 +5,6 @@ - - ")?; - } + // // #[cfg(feature = "document")] + // { + use dioxus_interpreter_js::INITIALIZE_STREAMING_JS; + write!(to, "")?; + // } Ok(()) } @@ -718,21 +717,6 @@ impl FullstackHTMLTemplate { Ok(()) } - - /// Wrap a body in the template - pub fn wrap_body( - &self, - to: &mut R, - virtual_dom: &VirtualDom, - body: impl std::fmt::Display, - ) -> Result<(), dioxus_isrg::IncrementalRendererError> { - self.render_head(to, virtual_dom)?; - write!(to, "{body}")?; - self.render_after_main(to, virtual_dom)?; - self.render_after_body(to)?; - - Ok(()) - } } fn pre_renderer() -> Renderer { diff --git a/packages/server/src/router.rs b/packages/server/src/router.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/packages/server/src/router.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 8d19a0b32f..f3cc44a8a5 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -35,6 +35,7 @@ futures-channel = { workspace = true } serde_json = { version = "1.0", optional = true } serde = { version = "1.0", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } + ciborium = { workspace = true, optional = true } async-trait = { workspace = true, optional = true } gloo-timers = { workspace = true, optional = true, features = ["futures"] } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 83eacec57e..bc24190385 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -64,25 +64,25 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { let mut hydration_receiver: Option> = None; - // If we are hydrating, then the hotreload message might actually have a patch for us to apply. - // Let's wait for a moment to see if we get a hotreload message before we start hydrating. - // That way, the hydration will use the same functions that the server used to serialize the data. - #[cfg(all(feature = "devtools", debug_assertions))] - loop { - let mut timeout = gloo_timers::future::TimeoutFuture::new(100).fuse(); - futures_util::select! { - msg = hotreload_rx.next() => { - if let Some(msg) = msg { - if msg.for_build_id == Some(dioxus_cli_config::build_id()) { - dioxus_devtools::apply_changes(&virtual_dom, &msg); - } - } - } - _ = &mut timeout => { - break; - } - } - } + // // If we are hydrating, then the hotreload message might actually have a patch for us to apply. + // // Let's wait for a moment to see if we get a hotreload message before we start hydrating. + // // That way, the hydration will use the same functions that the server used to serialize the data. + // #[cfg(all(feature = "devtools", debug_assertions))] + // loop { + // let mut timeout = gloo_timers::future::TimeoutFuture::new(100).fuse(); + // futures_util::select! { + // msg = hotreload_rx.next() => { + // if let Some(msg) = msg { + // if msg.for_build_id == Some(dioxus_cli_config::build_id()) { + // dioxus_devtools::apply_changes(&virtual_dom, &msg); + // } + // } + // } + // _ = &mut timeout => { + // break; + // } + // } + // } if should_hydrate { #[cfg(feature = "hydrate")] @@ -154,8 +154,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { // if virtual dom has nothing, wait for it to have something before requesting idle time // if there is work then this future resolves immediately. #[cfg(all(feature = "devtools", debug_assertions))] - let hotreload_msg; - + let template; #[allow(unused)] let mut hydration_work: Option = None; @@ -174,13 +173,13 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { let mut devtools_next = hotreload_rx.select_next_some(); select! { _ = work => { - hotreload_msg = None; + template = None; }, new_template = devtools_next => { - hotreload_msg = Some(new_template); + template = Some(new_template); }, hydration_data = rx_hydration => { - hotreload_msg = None; + template = None; #[cfg(feature = "hydrate")] { hydration_work = Some(hydration_data); @@ -203,8 +202,10 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { } } } + #[cfg(all(feature = "devtools", debug_assertions))] - if let Some(hr_msg) = hotreload_msg { + if let Some(hr_msg) = template { + // Replace all templates dioxus_devtools::apply_changes(&virtual_dom, &hr_msg); if !hr_msg.assets.is_empty() { From 80ba4acb04f6f85ee7404b71caaac9a4b593804b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 6 May 2025 20:45:51 -0700 Subject: [PATCH 296/301] fix playwright! --- packages/cli/src/build/request.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 9510480b51..1bba47fa27 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -2735,14 +2735,14 @@ session_cache_dir: {}"#, pub(crate) fn default_platforms(package: &krates::cm::Package) -> Vec { let mut platforms = vec![]; - // Attempt to discover the platform directly from the dioxus dependency - if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == "dioxus") { - for f in dxs.features.iter() { - if let Some(platform) = Platform::autodetect_from_cargo_feature(f) { - platforms.push(platform); - } - } - } + // // Attempt to discover the platform directly from the dioxus dependency + // if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == "dioxus") { + // for f in dxs.features.iter() { + // if let Some(platform) = Platform::autodetect_from_cargo_feature(f) { + // platforms.push(platform); + // } + // } + // } let Some(default) = package.features.get("default") else { return platforms; From 13e1db8e03ffc4a466a5d0faa3e6d204527a9668 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 6 May 2025 21:49:11 -0700 Subject: [PATCH 297/301] add barebones template test harness --- Cargo.lock | 228 +- Cargo.toml | 1 + packages/cli/Cargo.toml | 2 +- packages/cli/src/build/request.rs | 219 +- packages/cli/src/cli/build.rs | 2 +- packages/cli/src/cli/create.rs | 11 + packages/cli/src/cli/init.rs | 3 + .../barebones-template/.gitignore | 7 + .../barebones-template/Cargo.lock | 5295 +++++++++++++++++ .../barebones-template/Cargo.toml | 16 + .../barebones-template/Dioxus.toml | 21 + .../barebones-template/README.md | 25 + .../barebones-template/assets/favicon.ico | Bin 0 -> 132770 bytes .../barebones-template/assets/header.svg | 20 + .../barebones-template/assets/main.css | 46 + .../barebones-template/src/main.rs | 37 + .../playwright-tests/playwright.config.js | 9 + 17 files changed, 5709 insertions(+), 233 deletions(-) create mode 100644 packages/playwright-tests/barebones-template/.gitignore create mode 100644 packages/playwright-tests/barebones-template/Cargo.lock create mode 100644 packages/playwright-tests/barebones-template/Cargo.toml create mode 100644 packages/playwright-tests/barebones-template/Dioxus.toml create mode 100644 packages/playwright-tests/barebones-template/README.md create mode 100644 packages/playwright-tests/barebones-template/assets/favicon.ico create mode 100644 packages/playwright-tests/barebones-template/assets/header.svg create mode 100644 packages/playwright-tests/barebones-template/assets/main.css create mode 100644 packages/playwright-tests/barebones-template/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 53ce0a4635..a73f933d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1343,6 +1343,13 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "barebones-template-test" +version = "0.1.0" +dependencies = [ + "dioxus", +] + [[package]] name = "base16" version = "0.2.1" @@ -1811,9 +1818,9 @@ dependencies = [ [[package]] name = "built" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" dependencies = [ "git2", ] @@ -1985,9 +1992,9 @@ dependencies = [ [[package]] name = "cargo-generate" -version = "0.22.1" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd20c031c5650a045e60c7bc274aa2a20d32fd604b9265e760562ceda4bdbf26" +checksum = "dd3cea99ecf678f9f12dd63f685f9ae79dca897ebb2a8a0aa1ec4dfb8b64d534" dependencies = [ "anstyle", "anyhow", @@ -2012,7 +2019,6 @@ dependencies = [ "log", "names", "paste", - "path-absolutize", "regex", "remove_dir_all", "rhai", @@ -2037,15 +2043,15 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f905f68f8cb8a8182592d9858a5895360f0a5b08b6901fdb10498fb91829804" +checksum = "e788664537bc508c6f252ca8b0e64275d89ca3ce11aeb71452a3554f390e3a65" dependencies = [ "semver 1.0.26", "serde", "serde-untagged", "serde-value", - "thiserror 1.0.69", + "thiserror 2.0.12", "toml", "unicode-xid", "url", @@ -5117,6 +5123,19 @@ name = "faster-hex" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] [[package]] name = "fastrand" @@ -5388,9 +5407,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb60e7409f34ef959985bc9d9c5ee8f5db24ee46ed9775850548021710f807f" +checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa" dependencies = [ "autocfg", "tokio", @@ -5883,9 +5902,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.19.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ "bitflags 2.9.0", "libc", @@ -5898,27 +5917,27 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc19e312cd45c4a66cd003f909163dc2f8e1623e30a0c0c6df3776e89b308665" +checksum = "f438c87d4028aca4b82f82ba8d8ab1569823cfb3e5bc5fa8456a71678b2a20e7" dependencies = [ "bstr", "gix-date", "gix-utils", "itoa 1.0.15", - "thiserror 1.0.69", - "winnow 0.6.26", + "thiserror 2.0.12", + "winnow 0.7.6", ] [[package]] name = "gix-config" -version = "0.40.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e797487e6ca3552491de1131b4f72202f282fb33f198b1c34406d765b42bb0" +checksum = "9c6f830bf746604940261b49abf7f655d2c19cadc9f4142ae9379e3a316e8cfa" dependencies = [ "bstr", "gix-config-value", - "gix-features", + "gix-features 0.41.1", "gix-glob", "gix-path", "gix-ref", @@ -5926,9 +5945,9 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.12", "unicode-bom", - "winnow 0.6.26", + "winnow 0.7.6", ] [[package]] @@ -5958,80 +5977,120 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.38.2" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69" +checksum = "016d6050219458d14520fe22bdfdeb9cb71631dec9bc2724767c983f60109634" dependencies = [ - "gix-hash", + "gix-path", "gix-trace", "gix-utils", "libc", "prodash", - "sha1_smol", "walkdir", ] +[[package]] +name = "gix-features" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" +dependencies = [ + "gix-trace", + "libc", + "prodash", +] + [[package]] name = "gix-fs" -version = "0.11.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575" +checksum = "951e886120dc5fa8cac053e5e5c89443f12368ca36811b2e43d1539081f9c111" dependencies = [ + "bstr", "fastrand", - "gix-features", + "gix-features 0.41.1", + "gix-path", "gix-utils", + "thiserror 2.0.12", ] [[package]] name = "gix-glob" -version = "0.16.5" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111" +checksum = "20972499c03473e773a2099e5fd0c695b9b72465837797a51a43391a1635a030" dependencies = [ "bitflags 2.9.0", "bstr", - "gix-features", + "gix-features 0.41.1", "gix-path", ] [[package]] name = "gix-hash" -version = "0.14.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" +checksum = "834e79722063958b03342edaa1e17595cd2939bb2b3306b3225d0815566dcb49" dependencies = [ - "faster-hex", - "thiserror 1.0.69", + "faster-hex 0.9.0", + "gix-features 0.41.1", + "sha1-checked", + "thiserror 2.0.12", +] + +[[package]] +name = "gix-hash" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" +dependencies = [ + "faster-hex 0.10.0", + "gix-features 0.42.1", + "sha1-checked", + "thiserror 2.0.12", +] + +[[package]] +name = "gix-hashtable" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" +dependencies = [ + "gix-hash 0.18.0", + "hashbrown 0.14.5", + "parking_lot", ] [[package]] name = "gix-lock" -version = "14.0.0" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bc7fe297f1f4614774989c00ec8b1add59571dc9b024b4c00acb7dedd4e19d" +checksum = "df47b8f11c34520db5541bc5fc9fbc8e4b0bdfcec3736af89ccb1a5728a0126f" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "gix-object" -version = "0.44.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5b801834f1de7640731820c2df6ba88d95480dc4ab166a5882f8ff12b88efa" +checksum = "4943fcdae6ffc135920c9ea71e0362ed539182924ab7a85dd9dac8d89b0dd69a" dependencies = [ "bstr", "gix-actor", "gix-date", - "gix-features", - "gix-hash", + "gix-features 0.41.1", + "gix-hash 0.17.0", + "gix-hashtable", + "gix-path", "gix-utils", "gix-validate", "itoa 1.0.15", "smallvec", - "thiserror 1.0.69", - "winnow 0.6.26", + "thiserror 2.0.12", + "winnow 0.7.6", ] [[package]] @@ -6049,14 +6108,14 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.47.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0d8406ebf9aaa91f55a57f053c5a1ad1a39f60fdf0303142b7be7ea44311e5" +checksum = "b2e1f7eb6b7ce82d2d19961f74bd637bab3ea79b1bc7bfb23dbefc67b0415d8b" dependencies = [ "gix-actor", - "gix-features", + "gix-features 0.41.1", "gix-fs", - "gix-hash", + "gix-hash 0.17.0", "gix-lock", "gix-object", "gix-path", @@ -6064,8 +6123,8 @@ dependencies = [ "gix-utils", "gix-validate", "memmap2", - "thiserror 1.0.69", - "winnow 0.6.26", + "thiserror 2.0.12", + "winnow 0.7.6", ] [[package]] @@ -6082,9 +6141,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "14.0.2" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046b4927969fa816a150a0cda2e62c80016fe11fb3c3184e4dddf4e542f108aa" +checksum = "3d6de439bbb9a5d3550c9c7fab0e16d2d637d120fcbe0dfbc538772a187f099b" dependencies = [ "gix-fs", "libc", @@ -6101,9 +6160,9 @@ checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" [[package]] name = "gix-utils" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" +checksum = "189f8724cf903e7fd57cfe0b7bc209db255cacdcb22c781a022f52c3a774f8d0" dependencies = [ "fastrand", "unicode-normalization", @@ -6584,6 +6643,15 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -6686,6 +6754,16 @@ dependencies = [ "http 1.3.1", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -8022,9 +8100,9 @@ dependencies = [ [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.1+1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" dependencies = [ "cc", "libc", @@ -10608,9 +10686,13 @@ dependencies = [ [[package]] name = "prodash" -version = "28.0.0" +version = "29.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +dependencies = [ + "log", + "parking_lot", +] [[package]] name = "profiling" @@ -11205,9 +11287,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "remove_dir_all" -version = "0.8.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a694f9e0eb3104451127f6cc1e5de55f59d3b1fc8c5ddfaeb6f1e716479ceb4a" +checksum = "808cc0b475acf76adf36f08ca49429b12aad9f678cb56143d5b3cb49b9a1dd08" dependencies = [ "cfg-if", "cvt", @@ -11372,9 +11454,9 @@ dependencies = [ [[package]] name = "rhai" -version = "1.20.1" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0277a46f29fe3b3eb10821ca2c65a4751b686b6c84422aae31695ba167b0fbc" +checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" dependencies = [ "ahash 0.8.11", "bitflags 2.9.0", @@ -11799,11 +11881,10 @@ dependencies = [ [[package]] name = "sanitize-filename" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" dependencies = [ - "lazy_static", "regex", ] @@ -14120,14 +14201,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.2", "once_cell", - "rustix 0.38.44", + "rustix 1.0.5", "windows-sys 0.59.0", ] @@ -16953,15 +17034,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.6" diff --git a/Cargo.toml b/Cargo.toml index 18b9923d1f..e5d392ee19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,7 @@ members = [ # Playwright tests "packages/playwright-tests/liveview", "packages/playwright-tests/web", + "packages/playwright-tests/barebones-template", "packages/playwright-tests/fullstack", "packages/playwright-tests/fullstack-mounted", "packages/playwright-tests/fullstack-routing", diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 0593dec3fa..09b87db1df 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -77,7 +77,7 @@ which = { version = "7.0.2" } # plugin packages open = { workspace = true } -cargo-generate = "0.22.1" +cargo-generate = "0.23.3" toml_edit = "0.22.24" # formatting diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 1bba47fa27..66658c3cee 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -368,7 +368,7 @@ pub(crate) struct BuildRequest { pub(crate) profile: String, pub(crate) release: bool, pub(crate) platform: Platform, - pub(crate) default_platforms: Vec, + pub(crate) enabled_platforms: Vec, pub(crate) triple: Triple, pub(crate) _device: bool, pub(crate) package: String, @@ -532,25 +532,37 @@ impl BuildRequest { })? .clone(); - let default_platforms = Self::default_platforms(main_package); - let default_platform = default_platforms.iter().find(|p| **p != Platform::Server); + // The crate might enable multiple platforms or no platforms at + // We collect all the platforms it enables first and then select based on the --platform arg + let enabled_platforms = + Self::enabled_cargo_toml_platforms(main_package, args.no_default_features); let mut features = args.features.clone(); let mut no_default_features = args.no_default_features; - // The user passed --platform XYZ but already has `default = ["ABC"]` in their Cargo.toml - // We want to strip out the default platform and use the one they passed, setting no-default-features - if args.platform.is_some() && default_platform.is_some() { - Self::platformless_features(main_package); - no_default_features = true; - } + let platform: Platform = match args.platform { + Some(platform) => match enabled_platforms.len() { + 0 => platform, - // Inherit the platform from the args, or auto-detect it - let platform = args - .platform - .map(Some) - .unwrap_or_else(|| Self::autodetect_platform(&workspace, main_package).map(|a| a.0)) - .context("No platform was specified and could not be auto-detected. Please specify a platform with `--platform ` or set a default platform using a cargo feature.")?; + // The user passed --platform XYZ but already has `default = ["ABC"]` in their Cargo.toml or dioxus = { features = ["abc"] } + // We want to strip out the default platform and use the one they passed, setting no-default-features + _ => { + features.extend(Self::platformless_features(main_package)); + no_default_features = true; + platform + } + }, + 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()), + 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()) + } + }, + }; // Add any features required to turn on the client features.push(Self::feature_for_platform(main_package, platform)); @@ -675,7 +687,7 @@ session_cache_dir: {}"#, _device: device, workspace, config, - default_platforms, + enabled_platforms, custom_target_dir: None, custom_linker, link_args_file, @@ -2511,101 +2523,6 @@ session_cache_dir: {}"#, self.crate_target.kind[0] } - /// Try to autodetect the platform from the package by reading its features - /// - /// Read the default-features list and/or the features list on dioxus to see if we can autodetect the platform - fn autodetect_platform( - ws: &Workspace, - package: &krates::cm::Package, - ) -> Option<(Platform, String)> { - let krate = ws.krates.krates_by_name("dioxus").next()?; - - // We're going to accumulate the platforms that are enabled - // This will let us create a better warning if multiple platforms are enabled - let manually_enabled_platforms = ws - .krates - .get_enabled_features(krate.kid)? - .iter() - // detect the platform from the enabled list - .flat_map(|feature| { - Platform::autodetect_from_cargo_feature(feature) - .filter(|platform| *platform != Platform::Server) - .map(|f| (f, feature.to_string())) - }) - // filter down only if the feature is enabled on this crate or if it's a direct dependency of dioxus itself - .filter(|(_platform, feature)| { - ws.krates - .get_deps(krate.node_id) - .any(|(node, _)| match node { - krates::Node::Feature { krate_index, name } => { - name == feature && ws.krates[*krate_index].name == "dioxus" - } - _ => false, - }) - || package - .dependencies - .iter() - .any(|dep| dep.name == "dioxus" && dep.features.contains(feature)) - }) - .collect::>(); - - tracing::debug!("Manually enabled platforms: {manually_enabled_platforms:?}"); - - if manually_enabled_platforms.len() > 1 { - tracing::error!("Multiple platforms are enabled. Please specify a platform with `--platform ` or set a single default platform using a cargo feature."); - for platform in manually_enabled_platforms { - tracing::error!(" - {platform:?}"); - } - return None; - } - - if manually_enabled_platforms.len() == 1 { - return manually_enabled_platforms.first().cloned(); - } - - // Let's try and find the list of platforms from the feature list - // This lets apps that specify web + server to work without specifying the platform. - // This is because we treat `server` as a binary thing rather than a dedicated platform, so at least we can disambiguate it - let possible_platforms = package - .features - .iter() - .filter_map(|(feature, _features)| { - match Platform::autodetect_from_cargo_feature(feature) { - Some(platform) => Some((platform, feature.to_string())), - None => { - let auto_implicit = _features - .iter() - .filter_map(|f| { - if !f.starts_with("dioxus?/") && !f.starts_with("dioxus/") { - return None; - } - - let rest = f - .trim_start_matches("dioxus/") - .trim_start_matches("dioxus?/"); - - Platform::autodetect_from_cargo_feature(rest) - }) - .collect::>(); - - if auto_implicit.len() == 1 { - Some((auto_implicit.first().copied().unwrap(), feature.to_string())) - } else { - None - } - } - } - }) - .filter(|platform| platform.0 != Platform::Server) - .collect::>(); - - if possible_platforms.len() == 1 { - return possible_platforms.first().cloned(); - } - - None - } - /// Get the features required to build for the given platform fn feature_for_platform(package: &krates::cm::Package, platform: Platform) -> String { // Try to find the feature that activates the dioxus feature for the given platform @@ -2700,54 +2617,48 @@ session_cache_dir: {}"#, .map(|krate| krate.krate.version.to_string()) } - // pub(crate) fn default_platform(package: &krates::cm::Package) -> Option { - // let default = package.features.get("default")?; - - // // we only trace features 1 level deep.. - // for feature in default.iter() { - // // If the user directly specified a platform we can just use that. - // if feature.starts_with("dioxus/") { - // let dx_feature = feature.trim_start_matches("dioxus/"); - // let auto = Platform::autodetect_from_cargo_feature(dx_feature); - // if auto.is_some() { - // return auto; - // } - // } - - // // If the user is specifying an internal feature that points to a platform, we can use that - // let internal_feature = package.features.get(feature); - // if let Some(internal_feature) = internal_feature { - // for feature in internal_feature { - // if feature.starts_with("dioxus/") { - // let dx_feature = feature.trim_start_matches("dioxus/"); - // let auto = Platform::autodetect_from_cargo_feature(dx_feature); - // if auto.is_some() { - // return auto; - // } - // } - // } - // } - // } - - // None - // } - - pub(crate) fn default_platforms(package: &krates::cm::Package) -> Vec { + /// Return the platforms that are enabled for the package + /// + /// Ideally only one platform is enabled but we need to be able to + pub(crate) fn enabled_cargo_toml_platforms( + package: &krates::cm::Package, + no_default_features: bool, + ) -> Vec { let mut platforms = vec![]; - // // Attempt to discover the platform directly from the dioxus dependency - // if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == "dioxus") { - // for f in dxs.features.iter() { - // if let Some(platform) = Platform::autodetect_from_cargo_feature(f) { - // platforms.push(platform); - // } - // } - // } + // Attempt to discover the platform directly from the dioxus dependency + // + // [dependencies] + // dioxus = { features = ["web"] } + // + if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == "dioxus") { + for f in dxs.features.iter() { + if let Some(platform) = Platform::autodetect_from_cargo_feature(f) { + platforms.push(platform); + } + } + } + + // Start searching through the default features + // + // [features] + // default = ["dioxus/web"] + // + // or + // + // [features] + // default = ["web"] + // web = ["dioxus/web"] + if no_default_features { + return platforms; + } let Some(default) = package.features.get("default") else { return platforms; }; + tracing::debug!("Default features: {default:?}"); + // we only trace features 1 level deep.. for feature in default.iter() { // If the user directly specified a platform we can just use that. @@ -3344,7 +3255,9 @@ session_cache_dir: {}"#, static INITIALIZED: OnceCell> = OnceCell::new(); let success = INITIALIZED.get_or_init(|| { - // _ = remove_dir_all(self.exe_dir()); + if self.platform != Platform::Server { + _ = remove_dir_all(self.exe_dir()); + } self.flush_session_cache(); diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 13f73fa18a..df48e35883 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -109,7 +109,7 @@ impl BuildArgs { // we didn't build BuildRequest to be generally mutable. let client = BuildRequest::new(&self.build_arguments, workspace.clone()).await?; let default_server = client - .default_platforms + .enabled_platforms .iter() .any(|p| *p == Platform::Server); diff --git a/packages/cli/src/cli/create.rs b/packages/cli/src/cli/create.rs index 913e5aa06f..b4cb8532a0 100644 --- a/packages/cli/src/cli/create.rs +++ b/packages/cli/src/cli/create.rs @@ -58,6 +58,9 @@ impl Create { // If no template is specified, use the default one and set the branch to the latest release. resolve_template_and_branch(&mut self.template, &mut self.branch); + // cargo-generate requires the path to be created first. + std::fs::create_dir_all(&self.path)?; + let args = GenerateArgs { define: self.option, destination: Some(self.path), @@ -77,11 +80,19 @@ impl Create { tag: self.tag, ..Default::default() }, + verbose: crate::logging::VERBOSITY + .get() + .map(|f| f.verbose) + .unwrap_or(false), ..Default::default() }; + restore_cursor_on_sigint(); + tracing::debug!(dx_src = ?TraceSrc::Dev, "Creating new project with args: {args:#?}"); let path = cargo_generate::generate(args)?; + _ = post_create(&path); + Ok(StructuredOutput::Success) } } diff --git a/packages/cli/src/cli/init.rs b/packages/cli/src/cli/init.rs index 46c55ef967..0712947a5c 100644 --- a/packages/cli/src/cli/init.rs +++ b/packages/cli/src/cli/init.rs @@ -55,6 +55,9 @@ impl Init { // If no template is specified, use the default one and set the branch to the latest release. create::resolve_template_and_branch(&mut self.template, &mut self.branch); + // cargo-generate requires the path to be created first. + std::fs::create_dir_all(&self.path)?; + let args = GenerateArgs { define: self.option, destination: Some(self.path), diff --git a/packages/playwright-tests/barebones-template/.gitignore b/packages/playwright-tests/barebones-template/.gitignore new file mode 100644 index 0000000000..80aab8ea95 --- /dev/null +++ b/packages/playwright-tests/barebones-template/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target +.DS_Store + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/packages/playwright-tests/barebones-template/Cargo.lock b/packages/playwright-tests/barebones-template/Cargo.lock new file mode 100644 index 0000000000..c4820952eb --- /dev/null +++ b/packages/playwright-tests/barebones-template/Cargo.lock @@ -0,0 +1,5295 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.1", + "raw-window-handle 0.6.2", + "serde", + "serde_repr", + "tokio", + "url", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.9.0", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.9.0", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-serialize" +version = "0.6.3" +dependencies = [ + "const-serialize-macro", + "serde", +] + +[[package]] +name = "const-serialize-macro" +version = "0.6.3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "const-str" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e991226a70654b49d34de5ed064885f0bef0348a8e70018b8ff1ac80aa984a2" + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.101", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dioxus" +version = "0.6.3" +dependencies = [ + "dioxus-cli-config", + "dioxus-config-macro", + "dioxus-config-macros", + "dioxus-core", + "dioxus-core-macro", + "dioxus-desktop", + "dioxus-devtools", + "dioxus-document", + "dioxus-fullstack", + "dioxus-history", + "dioxus-hooks", + "dioxus-html", + "dioxus-logger", + "dioxus-mobile", + "dioxus-signals", + "dioxus-web", + "manganis", + "subsecond", + "warnings", +] + +[[package]] +name = "dioxus-asset-resolver" +version = "0.6.3" +dependencies = [ + "dioxus-cli-config", + "http", + "infer", + "jni", + "ndk", + "ndk-context", + "ndk-sys", + "thiserror 2.0.12", + "urlencoding", +] + +[[package]] +name = "dioxus-cli-config" +version = "0.6.3" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "dioxus-config-macro" +version = "0.6.3" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "dioxus-config-macros" +version = "0.6.3" + +[[package]] +name = "dioxus-core" +version = "0.6.3" +dependencies = [ + "const_format", + "dioxus-core-types", + "futures-channel", + "futures-util", + "generational-box", + "longest-increasing-subsequence", + "rustc-hash 2.1.1", + "rustversion", + "serde", + "slab", + "slotmap", + "subsecond", + "tracing", + "warnings", +] + +[[package]] +name = "dioxus-core-macro" +version = "0.6.3" +dependencies = [ + "convert_case 0.8.0", + "dioxus-rsx", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dioxus-core-types" +version = "0.6.3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "dioxus-desktop" +version = "0.6.3" +dependencies = [ + "async-trait", + "base64", + "cocoa", + "core-foundation", + "dioxus-asset-resolver", + "dioxus-cli-config", + "dioxus-core", + "dioxus-devtools", + "dioxus-document", + "dioxus-history", + "dioxus-hooks", + "dioxus-html", + "dioxus-interpreter-js", + "dioxus-signals", + "dunce", + "futures-channel", + "futures-util", + "generational-box", + "global-hotkey", + "infer", + "jni", + "lazy-js-bundle", + "muda", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "objc_id", + "once_cell", + "rfd", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "signal-hook", + "slab", + "tao", + "thiserror 2.0.12", + "tokio", + "tracing", + "tray-icon", + "urlencoding", + "webbrowser", + "wry", +] + +[[package]] +name = "dioxus-devtools" +version = "0.6.3" +dependencies = [ + "dioxus-cli-config", + "dioxus-core", + "dioxus-devtools-types", + "dioxus-signals", + "serde", + "serde_json", + "subsecond", + "thiserror 2.0.12", + "tracing", + "tungstenite", + "warnings", +] + +[[package]] +name = "dioxus-devtools-types" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "serde", + "subsecond-types", +] + +[[package]] +name = "dioxus-document" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "dioxus-core-macro", + "dioxus-core-types", + "dioxus-html", + "futures-channel", + "futures-util", + "generational-box", + "lazy-js-bundle", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "dioxus-fullstack" +version = "0.6.3" +dependencies = [ + "base64", + "bytes", + "ciborium", + "dioxus-devtools", + "dioxus-fullstack-hooks", + "dioxus-fullstack-protocol", + "dioxus-history", + "dioxus-lib", + "dioxus-web", + "dioxus_server_macro", + "futures-channel", + "futures-util", + "generational-box", + "once_cell", + "serde", + "server_fn", + "tracing", + "web-sys", +] + +[[package]] +name = "dioxus-fullstack-hooks" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "dioxus-fullstack-protocol", + "dioxus-hooks", + "dioxus-signals", + "futures-channel", + "serde", +] + +[[package]] +name = "dioxus-fullstack-protocol" +version = "0.6.3" +dependencies = [ + "base64", + "ciborium", + "dioxus-core", + "serde", + "tracing", +] + +[[package]] +name = "dioxus-history" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "tracing", +] + +[[package]] +name = "dioxus-hooks" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "dioxus-signals", + "futures-channel", + "futures-util", + "generational-box", + "rustversion", + "slab", + "tracing", + "warnings", +] + +[[package]] +name = "dioxus-html" +version = "0.6.3" +dependencies = [ + "async-trait", + "dioxus-core", + "dioxus-core-macro", + "dioxus-core-types", + "dioxus-hooks", + "dioxus-html-internal-macro", + "enumset", + "euclid", + "futures-channel", + "generational-box", + "keyboard-types", + "lazy-js-bundle", + "rustversion", + "serde", + "serde_json", + "serde_repr", + "tracing", +] + +[[package]] +name = "dioxus-html-internal-macro" +version = "0.6.3" +dependencies = [ + "convert_case 0.8.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dioxus-interpreter-js" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "dioxus-core-types", + "dioxus-html", + "js-sys", + "lazy-js-bundle", + "rustc-hash 2.1.1", + "serde", + "sledgehammer_bindgen", + "sledgehammer_utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "dioxus-lib" +version = "0.6.3" +dependencies = [ + "dioxus-config-macro", + "dioxus-core", + "dioxus-core-macro", + "dioxus-document", + "dioxus-history", + "dioxus-hooks", + "dioxus-html", + "dioxus-rsx", + "dioxus-signals", + "warnings", +] + +[[package]] +name = "dioxus-logger" +version = "0.6.3" +dependencies = [ + "console_error_panic_hook", + "dioxus-cli-config", + "tracing", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "dioxus-mobile" +version = "0.6.3" +dependencies = [ + "dioxus-cli-config", + "dioxus-desktop", + "dioxus-lib", + "jni", + "libc", + "once_cell", +] + +[[package]] +name = "dioxus-rsx" +version = "0.6.3" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dioxus-signals" +version = "0.6.3" +dependencies = [ + "dioxus-core", + "futures-channel", + "futures-util", + "generational-box", + "once_cell", + "parking_lot", + "rustc-hash 2.1.1", + "tracing", + "warnings", +] + +[[package]] +name = "dioxus-web" +version = "0.6.3" +dependencies = [ + "async-trait", + "dioxus-cli-config", + "dioxus-core", + "dioxus-core-types", + "dioxus-devtools", + "dioxus-document", + "dioxus-fullstack-protocol", + "dioxus-history", + "dioxus-html", + "dioxus-interpreter-js", + "dioxus-signals", + "futures-channel", + "futures-util", + "generational-box", + "gloo-timers", + "js-sys", + "lazy-js-bundle", + "rustc-hash 2.1.1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "dioxus_server_macro" +version = "0.6.3" +dependencies = [ + "proc-macro2", + "quote", + "server_fn_macro", + "syn 2.0.101", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +dependencies = [ + "bitflags 2.9.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.0", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "enumset" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a6b7c3d347de0a9f7bfd2f853be43fe32fa6fac30c70f6d6d67a1e936b87ee" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da3ea9e1d1a3b1593e15781f930120e72aa7501610b2f82e5b6739c72e8eac5" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generational-box" +version = "0.6.3" +dependencies = [ + "parking_lot", + "tracing", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "global-hotkey" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fbb3a4e56c901ee66c190fdb3fa08344e6d09593cc6c61f8eb9add7144b271" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2", + "objc2-app-kit", + "once_cell", + "thiserror 2.0.12", + "windows-sys 0.59.0", + "x11-dl", +] + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.15", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa 1.0.15", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy-js-bundle" +version = "0.6.3" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", +] + +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "longest-increasing-subsequence" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "manganis" +version = "0.6.3" +dependencies = [ + "const-serialize", + "manganis-core", + "manganis-macro", +] + +[[package]] +name = "manganis-core" +version = "0.6.3" +dependencies = [ + "const-serialize", + "dioxus-cli-config", + "dioxus-core-types", + "serde", +] + +[[package]] +name = "manganis-macro" +version = "0.6.3" +dependencies = [ + "dunce", + "macro-string", + "manganis-core", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix 0.38.44", +] + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "muda" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "libxdo", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "myapp" +version = "0.1.0" +dependencies = [ + "dioxus", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.0", + "dispatch2 0.3.0", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.0", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.0", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.26", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "version_check", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfd" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d" +dependencies = [ + "ashpd", + "block2", + "dispatch2 0.2.0", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "pollster", + "raw-window-handle 0.6.2", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa 1.0.15", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.15", + "ryu", + "serde", +] + +[[package]] +name = "server_fn" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b0f92b9d3a62c73f238ac21f7a09f15bad335a9d1651514d9da80d2eaf8d4c" +dependencies = [ + "base64", + "bytes", + "const-str", + "const_format", + "dashmap", + "futures", + "gloo-net", + "http", + "js-sys", + "once_cell", + "pin-project-lite", + "reqwest", + "rustc_version", + "rustversion", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror 2.0.12", + "throw_error", + "tokio", + "tokio-tungstenite", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "341dd1087afe9f3e546c5979a4f0b6d55ac072e1201313f86e7fe364223835ac" +dependencies = [ + "const_format", + "convert_case 0.8.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.101", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ab934f581482a66da82f2b57b15390ad67c9ab85bd9a6c54bb65060fb1380" +dependencies = [ + "server_fn_macro", + "syn 2.0.101", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sledgehammer_bindgen" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e83e178d176459c92bc129cfd0958afac3ced925471b889b3a75546cfc4133" +dependencies = [ + "sledgehammer_bindgen_macro", + "wasm-bindgen", +] + +[[package]] +name = "sledgehammer_bindgen_macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f62f06db0370222f7f498ef478fce9f8df5828848d1d3517e3331936d7074f55" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "sledgehammer_utils" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdd4b83524961983cea3c55383b3910fd2f24fd13a188f5b091d2d504a61ae" +dependencies = [ + "rustc-hash 1.1.0", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "subsecond" +version = "0.6.3" +dependencies = [ + "js-sys", + "libc", + "libloading 0.8.6", + "memfd", + "memmap2", + "serde", + "subsecond-types", + "thiserror 2.0.12", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "subsecond-types" +version = "0.6.3" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.1", + "windows-core 0.61.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "throw_error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e42a6afdde94f3e656fae18f837cb9bbe500a5ac5de325b09f3ec05b9c28e3" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.7.10", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "tray-icon" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.1", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warnings" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f68998838dab65727c9b30465595c6f7c953313559371ca8bf31759b3680ad" +dependencies = [ + "pin-project", + "tracing", + "warnings-macro", +] + +[[package]] +name = "warnings-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5df295f8451142f1856b1bd86a606dfe9587d439bc036e319c827700dbd555e" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-implement 0.58.0", + "windows-interface 0.58.0", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "webview2-com-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" +dependencies = [ + "thiserror 1.0.69", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core 0.61.0", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.0", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +dependencies = [ + "windows-core 0.61.0", + "windows-link", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.0", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.2", + "windows-strings 0.3.1", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wry" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac0099a336829fbf54c26b5f620c68980ebbe37196772aeaf6118df4931b5cb0" +dependencies = [ + "base64", + "block", + "cocoa", + "core-graphics", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc", + "objc_id", + "once_cell", + "percent-encoding", + "raw-window-handle 0.6.2", + "sha2", + "soup3", + "tao-macros", + "thiserror 1.0.69", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2522b82023923eecb0b366da727ec883ace092e7887b61d3da5139f26b44da58" +dependencies = [ + "async-broadcast", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow 0.7.10", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d2e12843c75108c00c618c2e8ef9675b50b6ec095b36dc965f2e5aed463c15" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.10", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zvariant" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557e89d54880377a507c94cd5452f20e35d14325faf9d2958ebeadce0966c1b2" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow 0.7.10", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757779842a0d242061d24c28be589ce392e45350dfb9186dfd7a042a2e19870c" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.101", + "winnow 0.7.10", +] diff --git a/packages/playwright-tests/barebones-template/Cargo.toml b/packages/playwright-tests/barebones-template/Cargo.toml new file mode 100644 index 0000000000..f37c8eb6ed --- /dev/null +++ b/packages/playwright-tests/barebones-template/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "barebones-template-test" +version = "0.1.0" +authors = ["Jonathan Kelley "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus = { workspace = true, features = [] } + +[features] +default = ["web"] +web = ["dioxus/web"] +desktop = ["dioxus/desktop"] +mobile = ["dioxus/mobile"] diff --git a/packages/playwright-tests/barebones-template/Dioxus.toml b/packages/playwright-tests/barebones-template/Dioxus.toml new file mode 100644 index 0000000000..a66bb00f84 --- /dev/null +++ b/packages/playwright-tests/barebones-template/Dioxus.toml @@ -0,0 +1,21 @@ +[application] + +[web.app] + +# HTML title tag content +title = "myapp" + +# include `assets` in web platform +[web.resource] + +# Additional CSS style files +style = [] + +# Additional JavaScript files +script = [] + +[web.resource.dev] + +# Javascript code file +# serve: [dev-server] only +script = [] diff --git a/packages/playwright-tests/barebones-template/README.md b/packages/playwright-tests/barebones-template/README.md new file mode 100644 index 0000000000..1e629b6306 --- /dev/null +++ b/packages/playwright-tests/barebones-template/README.md @@ -0,0 +1,25 @@ +# Development + +Your new bare-bones project includes minimal organization with a single `main.rs` file and a few assets. + +``` +project/ +├─ assets/ # Any assets that are used by the app should be placed here +├─ src/ +│ ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app +├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project +``` + +### Serving Your App + +Run the following command in the root of your project to start developing with the default platform: + +```bash +dx serve +``` + +To run for a different platform, use the `--platform platform` flag. E.g. +```bash +dx serve --platform desktop +``` + diff --git a/packages/playwright-tests/barebones-template/assets/favicon.ico b/packages/playwright-tests/barebones-template/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..eed0c09735ab94e724c486a053c367cf7ee3d694 GIT binary patch literal 132770 zcmXV11yodB*S-V8Fm!hf4X+>_0@6x1Dj_g{Gy@1o$Iu}SA|RbADJ@-s2vX7=BHbzZ zU)T4u77H#hbI-Z^?7g4Z0004Cz`qX&fB=Mp0Kgjj9*zFrH5VKLWPm@DmHq!~c>w5& zf&l#d|GWOk4glK&;C~|i|C$&8l8zt%G5Gc0>)Ap9Kmr2;h|<8IHV3B(9uQcOr;BuOFb ze~4f-u16K~baSL1RuL6NfIAj93omjL$1cH?qyN;@wD}_Q_Ij;N%sbutoqF2gpK?Fb z;;gx$R+}Zab5mcGg|)m-p<_WxSB8iKzxVO0|9E(I@BNL9=?YW0xVcs8m@v@U*^J8E zpGr&dOe^2BB*MQ#LW$Wz5#9XX4=yCz-RoHa!6qggSsuIbHP0{Zg5)nKKWxcR>yibGmBS}?ep1TtWX6{{g>bT!G-hb^=+#n zd9yb@+ERv$1dq9~s;X*X?WpV_56{i*V7gFWj{BI(annu(-M(5sD~|N}m-whKJgOl< z{I$0H{CtroPo9{Bo1ZRe^(;6j9@GqP;Q2^ppE1U7+|AC;&Xi=jMt5d1Nj?hc>XH|* z9!&Etcp7^}L1M?;V~WXu$ryR5Rfamfo&^8a0o)Fml`cF!`u%|)tb`{U!zBgr(mtx* z-hZe3rI&`Lk@4;Cm0j8emKW*5M-7dPu6ClMqeD(E#Iaq59&J$9SpRJ5;E$1DR%E+_ zLFfN*!spW%{3-bF*>=h#YHo0K#FE>y=rSNE8V+v>%QKBK}Z63#rmae}HSE4x{A zG22o8hH6;g;MB-)k29xUPL1FQ-?cc^hh% zaTdjhiyKq!K$43p{DpI(I>K80Xj5pN|%)z5kOH%!E9IQihW^5% zdH;kRm*xexdgrCPK5Z`j>=p_+vXJlTzY>vYPpl5(KHzITp@2gv@Pl(Zg9VEQ)lm)( zJ7pg~dX<)zKCp?zcw{+R(Q>T%cdGsFY$w%(LESMFlO{&bkzY z$G%zb^2V$BVRJA8hZYj}S~H!;T5JWsaP2QWob2SZMD7OBMKbm|m5ty}Uv zXiZeV5C9YL*xAlh`?ta5y2Uy1KAG?8P&rbp6H4Un)<&LVKWFZW6j3lV)S3$;SW*5~Wt<|5jLn}y zhu18*%Cwh9p`+q9`XrxUqLs(6@R14~y$xb}y+V7fNLyl|q@OtW-P!@|?P~D6ce?N} zc}!1iaZFxoVbXPcm%xI~ISz-nn;lv+(*4rj9c`qy^Y@Z0pZWOs0$ss8&d202ZC>is zv{gK=#|BK9`tmY*EeFl+@9z&}eE2Xdg5S;1s`P_D=6jleCF2K4&wXbm@85~%?$;7$ z<9bxm*Sj_GVcjdAg94KkN04YZ8=Jkf|HEFB%V*S2-XZ%V1IMxO__?VaSw`l<85(XV z_wEDWln!v-+$)spO^pJOTcVW{aC~*PlcVNY!9?-9hZI3i_~GGu2WxS9&8AdZi> zgWdAR1rH}!bv6}HzfifcHWH~XtFL;53^Hd&InUMaZg2mm_U0x?Ey-WbG5v)3WYVU- zu8yHS;Pxsj)yl;Ce8%SfIxm8;S`T%2cYVNA?=V&IA-Hon5eT(1ylqQ%5sztVYH}74 z6N{HV859cq0v4aM(&y!>O_gAPrv6v-GU~2Z9Z8Ddy8KTmZ&xoTjHeWXn}8i4vH2`a zjsH|}`tWi=;Co_ew?bAy_ zGxY@pmb=>%rT6EnZ~3x6YaOOgX=u1`yZ<{J z7+^W)p^DjrnyZgeCFYofB8mDReyr?{!b#enDh)KV+~OJ6FF z!j&8}2K{Wob8A)YzYuV}_bS7h2F-Tk*O!(5U3MmEO|}co&L)eIagqI1#lm0&!H)Qj z6)rC~VbHOGWrtjr=ewH^BfcY`6V+!{N+5&f=HESUsx5F8~a)`Sc;}G@5X8w)LXj=`Y>x%?m2n zraYMzh}s0(L+O;IRope za$h|-_VXKw2WO7v(g4&PvItm}`(5e9$`P7-e0-egP3*cV-(t$A#$E2d7i`o$25b$k z=HSDGmRTUIcs6s&=#*-($n1R6N8#e)W*=YQItWGvxIB9{A-R$1rfFOaGchqSwa!l3 zJ%HNKAieyF1tl?a4MXZM>=;C@R5ZtqARouZ#$vwWVM~AuBB!FN8Cb_Hc9<#vz7c*~ z%EK&S9LIo?k~AvI!c_-8#BEcZ2Wm_>edJHMR*jgh^Onj!-`?KlTL`?rjW4zjoPXWd zDhB3$rlyw_t*hmjEX1=rXLmBpJtD(0_kL>C{@zlILiB{bdS|6*be}OyQ-+3qBmy06 zu(?55#Q$88oKe!laU)`K>zd|KCuZajAip(>^)8sK)&tJEHF-+-SF4M!+a;MyMiYxU zR8*seoir*G{X0Y`nOh(sJtC0n;@x&;fwPR46k};)<7MSqZ>;ZW?JrHWen{g{FWuk9 zwYY0fIl0a+JCo(tPuWP*p&gZVsfy&Vk#&z|vuv5bJLgnhKR1aTz?Uh!xHOV_i!J$TSP|J5x7 z1QoNF8#4DZn$1E0U&~=I#^H}qC8paeu-X4%Y-IEUk|rOSJzAh7<}_RT$$6&Q%I-qQ ze*ELHHdiebk;MTSwk-b2NicVFUq+N%JpsvHpJKzKUd$0ArT_l>uc=0&0}_+T4+OO5 z6s4@V@A1G`=-rNboL(Qxt-OlHN%_i#TNr~CpVVLzKDXxthlL#Ad*}aD_m~-wzK)Mh&wEE;on_D<9p_b47nhQn zdcGTf$3XZylqk2QCDY{Li&-&J$mSOm7bHQG><}wo4+uBIz!LN)AE`$TmA>Pqcq2^k_l1^J_!t*c%I@{l+!@a9`==L^2_CbTqCN^;1g@lrf4R z=yWF#8>)djX3fKMTw(|yQYl~7`Tad^$vh=qJqWz_ePd>3rt<^Jg%N5OjEmc8$nljF z{<)HhKB}WXPII@JnPq%(vQ2dURv-mTQU8!Dd#J72l5Q@qMM(N;V?qB4+o0qUgN{C+ zHBJP_P-Y8I#>K-U3cT7X!3%HJa>WU}o?9ZMl8=cexOp|CW8R1)e=qlnj>d{$ViNNF zJXbNdHRBQNZee9VK2K4T8vWyk>T}gItFiip>O9$z&{}7AfY=BfCLgAfwtDikA-6DZ zb#Ja=*tpHl+isR&Bax)-w1{tI!E=dWZf?$)+^v`W9FzaM@bZ8E!FG0^oBgOKo;KVV zB(xh3G^U9;~^{iby-}E$B86^>o5=Q-8+wTC!no z!Qkb~%+%LcI`TtOg?N-a2E&8gRz+}G%kT1TJ&QGIN*TQQd+^XvMjTIJOZ?y@3DTYI zZ9>BaCljNfB&o4AaK|V>_+BS#FUm@?oFj_u;$6TFB!wV=a%O`r4!XQz9|MzxxC6vz zwoJHmPNhEx(e2zcrB%O2@go5Gz?&l!k@O| zD=^~K)=!E8aOT{)a9#WDoV(MKQclgx%d6bSq|8Q~(!8wvdf{dq*8?d*)N9v7-@X!j zyIb_$U;r!m)UJD4Wb{XohnS2IcifJV6m3l-)u@V!hf|UVEhiK# zSE~89uQEE4?Hgf3|LCuHRUI9MkzcoY;cSl-h8M zCH{<>OOTD0mp~(~LiXkZNAG<+jwvBM+tIA6LMLSm6PH52G(B$Ts3L9T%r2iHD&p0l zRt|xdok%1WwWw}|6P7{^8epBCgOq+{97KDZb|eJ%O^90d#(a0ETqmSJ*!TeeNUEet zbn|zqkeTJT2YzbBhWw;?4O!K(rZv#r#Fj%xcH&6&e&K(XA8{VCiBT-i65EkCf6%sX zX*MJf=bK}I!IPbAuIyE!9yVYGmkk=j3FepmF_Sh&XMX1XbbXPOyH1i=J`|)_>cRB* zCq?k3CJp-Y=g*5>U0qrI3Qyux9Y0u^zt9e<(f><^pnqYAF&1~DZ|&G6b&hS}ZiXSJ zjM?^scDgHW(p$OYR1q--kYFsBX#49#dq)2ZC4S6wJ>6&OyZxyo{CX^c{E-!4Z*MOj zZZ6E>I|o->@ZmX9c6%}T${)7&9Yc(e+g;($(DoK9HU@pQ*7zN6H`XxNVO0TH0TxQc zz>IcT=N@mBub}F|fz(b}jVR$o9g&FZ51{32(m1HTzTTvNDt7$d%3F&mmGFU5T=< z8F>~zs5p`gz;OtIOFvSxI7X3D0RG~ZTeU>$B$@>;_TCQ|+1EFYxcc&+Y}KYs^O*{Ste% zzvRg{HT^8E&-a92_wNcAk@8U7d(=V4`={?As!AncpRoTU3rUg9>lgnz{dO+IAK;t{ zk0iKz72-kdAyL^8^+tseK@ zu~b1VR8D8gjb)Vx09hQR%BJnl14EB5<}>{w!)ZA)UAlhmOjWkCc;jIxcbrn?-b6kb z@{@j>z@rc(**r2eiP4`a7?u(_UTgPjad?9L2>4R}N{w-gn@q_iy5r ze~ptJ3U&KsQo`y;qZ92rtDeH(hS7nWxvn~CKOOXkDksdE^K&wnD>0rLB?ZOpN)R^V z_m8kHB@*ymK`y$0Lo5467@hLzLxylhw`jewd4g(t9Ghz`6bBvi8H2&Z6tLxNbw{i| zI?T$-a;pFz=HDq3&jlCHVaQt-aX$}`x@zepq38TY1yv>maP)cqLZzOGBsj_zQ3ksn zU*l+wYFia}&jjXOHD#JtzR@KxubgVGYiYR&>|WrzCIjyRK!QDf{N?Q(Z^vTY=BgYI zv36+t_?ft3uKS?0H76dH%Z+y7>)Rgt@kShh44u`V)b*(M?brLwGA8wohBGb~KZ7Dm zE1K+2hq5FqmB|H&T^xl-D+xb>Ydxn0>Np@p${sAJJhU8?x^wXRMq z##i#PTie@4)s}s6ArZ~agu?V7apQG=dr^YJtQw>^lLUp^^m8z4i`z*EH+RU(!((fs z!he&8OpI)n&S8{(4bXy&yu!6qOan=u=$B`AeF-(7^zym1lVRF1&;pJYmUtJt zwD0&N=ZC1IcJB9|AW`+@P$f~6v?#?D6eHHB0L&`8UmO<$eC>V#T;!jXh4n0nJBG#v zTzs|bFTK(j$$}vtgz>YAds)e$l0$9TQ)XLCr;4G|?TR1+$~};?f#Es}_^r_`P4g7J zOs`#Lci^Ya5Mgx2wXosBuvJuxcw1Y&lEDL?>p7M0%EK}xW@A%NC=7i}$G)$xnIql$ zYHO^hd*LxQltUu}`hGy9ySnTo-H`3az0DXxnIFEdqNn3=+SjQY{GHjO(5wlEUqE~$ zWdBVm+7`uS{dCt%DxZDiAKiE1nsi4OpD7C1~h#AYup}@+zW|XO!aXJz?wG6Um1dY2Mr56X!Dn<(+IMeB{PZ)*ZwINwa$ATXaye4v=8t+WOt8gnBrIX>JI!ZG(vFs{f+xqBWD#X`PLX zpD{>wnF8z^>QT*PqDWVI^^79}OG!%d*kA~R1Lu<-=lf)g6k$YR*sszbhc0eJi<^W! z6KPs-PjUJ?O<&*ZjMddu|Nn#-%(!j1^n)x28}kx)-lB5s0~JG)l9F&VG&CZxLpt>( zF*~@@_!*w)*;ui!!Nl7_l%269vIFqxaf-|5xr$ys_P;tU`Ij>@hcAY_G5NtPVUno) zdj(wDFyUP(8j!1jB*bDHV;C6C#IC8S0t}Gk2Uh7SR?{QI38Lni5r^GJ1ulP@%HcuG z`m57|fNl8z&w!7h$*S6a*!qr!$+5}*E!tG|EuA*c(sDx}$I|z9%X=RGP2Jz~^dB1p|e!>ZC`F;CM(QOf*|JGea zMTH(q;`c@NW`pkVr)9a?H59$Aye0+)`WTh{pQ3vJ0GeErk)o;m+9?mO=EkYz7uo9@ zIA-?fC8RQCTWhu7k{@50YsL1WX5>&mM*e5NjqF!Q^{?bW8hj22gkX|3%b7PKuWWNR zu*xuAO!w^U?4DtN=e{c8moxx~gFw&aPr6Op?#bWhg$@Hehf9Cp_2Ke}y`M%xRnu(r zhA#nyo@%_4%iO9cX5mMQ4&85mXk}r#xf6tnA_N=x@WWpbjFEcGIk{K*;6-O;B(Mbi z;)8)ns;R2#uyv*FjtK9OGXN}u#Q&QEP%*sE@@P_znT!nUGj8svs;;10ei!N-_o>6S zQqrNdQ|eq6jlj|FNeGWUj_2+DSo1KHxrN`bOY>q}5YZ1PDAdSz-#25o(oLSfxS=t) zWF2}xhP^BXicyxD6o5t;i8%n|f>nruMOANHE+p#cr7=|*5sHt5`l9eGG?EkHa!+aXZ&u(7Z}2(T^ODE&hc0?QTYHhDz3*6vDB zIG44~NL|M3;)^|N>dzQFrerL|IQ#=VZhN4f#U%PP1|kkF_Hay%uT>JHS?<~2syVoB zc4El3Qgpq|YE6igRl~9fS1zDsdxxf^O%RoSp%=^^#)y7(pCTMTCx8`V^!t;ZUX_~XG~xX%U2B74eiEva8?t%JQvDr7lS4X~zOwoQvX%Bcq=Q2PfQ zoSsrx%777?`jB+Rm&}2Gacz@8uPt2G{`9?h{2j7Ur^yQ^C3R-q_Q_k{SptpezniF$ z=UnAf5s}-VHsYKm;_!Uv&n>6I&M6g#T3_2sTrsP8W2F{zd2Q-6+HPoWJ@5U?sMG8d&3+tG%br|GIT z3~xM$R%B6{nwa2?k?d=&%%cA)A_uLK-O9Jr7PSe`-P@S2BTh219>U3d8WzuMCrc9^ zLOoFmQ*?ZCUutsclz&8j;>Ke}QuliN63z(#IUA+l}7GqBq0w4A()QpPySwN=OXRZb!FwhpolSWLLCZZJ&7TPQPYM z$aEd-L7;$i+gns*k4obCgY|YE)JQ~E5yxj|0 z-C-m)VDu z6R&bHc&CBy7J@7AQ-LfN#yh5ZkU^aF(T+sNILi+WjgjW7Qq+dc;o3gJn2(anNIxfZ<4H{fDiBTnw4~8|5281<}W_x z$WBEh?+Pgf9`565VtjK4?GP-b0ezxrHm6+oH*cPS$+2@_duK=JKV)DovNIS<-`M#2 z3-~0Kic)B?3$?_~hb5q7e1Bp1?H8B=C9MAb)BeM}n*qMw;{clsBS|NJ%zZ44(4S$j z@8}$iPx7VyA_M@JGs6MaAbq#6f8=FE)}EJ1Qjx#keqVo)H)Mf!Bz91G%!OsZWpn#q z7cs!$-E#RS)E-Tpba9BcO2QPrv$gf;_1X5sRKPfWFz7AdU1;$>AxhCr7PRBTClle! z#Pzh|HK6u@VWs?>My{PzkhpxHj#+&-YX+%_^X@y7k;4gNMADY3kK(>(S4jGE5T*04C{ z3v1og4_7u?Wg_}jM7%`z49~>@%1rGz-g^8*-Ea<&imSoGqm+`F_kV*x_RyiH%mQ0& zR(qn_nOPp}NxY+WK7HyEs3&%cy?h}g@LvqZjgN)MQ{SSRJ5qcOigM@oBgUxnvoi)E zw?BhjWrU*mX+k!H51V(Zzk%JGuPV3M4^ZtKJB&?7Cnak}@C%j{_6TA@&_z*;6qR|N z-Jb(&mO7fL1I@ySKY*R=bxHf}o^#^LekCS^brPF69=x^MQ2D$`P|ye)+*O%Ppns|o zQRJd(C7{a2jCvLgnIjX3UWjq+4tpV?0RImH4<8BPY!fKSo%DHXW5Zdjo__q?*mw?d zz5HL%kJ-67=W!#ZOs8HJXpp*CZ@?XH3d0xpcNXKMG}#d(1p2%!RzvKT)I-U)HXy;p zniPjnOYviQ`R(lo=eED|E*BF)!G8HZ|NO^gt^@#aNaw8?k+$*1_VN%Xcp1#YIIutNeeJlgui|)w8Xcb?V46>C&BVZ zURG6Qw31jp!JHbwl2)vutD2Eo_Q6{ zKz-HSn9#`Av&Z5batc-Ga9ZIB z!QBy;7xCZ5bCyE$x!pQ~^`a{YF(k>tC#Ot1ucuz(k98eQu*tdaF=Yx^_BK3h+RQip z_uMzWQ5R4jNu#}ZOj|BF+1c5Na1!TRhh6Nk$Bl89rpNI+agDU~Wrdp|Qk5eiOX?MJ zMJhT@vT>~Th<+FI)4%WYY*&T3sBBCYKSYr@+CJ^RZ4l4TvkNn#E>MaO_zPN>zCMt- zyy%5{Z435+MQU-?qdCx$x_2m)P!2;;xJL28)8?W>FE^$X*XWp6d*msh-=1KJ7mr8u zJo)T~#{(Z*@B65g^)^~>2v8>*OByl6{pi{we=Bnry)ROlY50OxCdMw~IVfPVw*UR< zEZ@C=jZJ$DLl7#4f+m3SG_YVlKH9DGvdpam$Pu}@VZBx#wvUGEHG58>S=89Bh5g z1*)t%Ip~6u>4;fYLE*I>M28nl-Tt@OEXOb;kR5Pkx7g}?QKLAHBR*6&-M8}Yfo+wZ z3Yx&(2)BJ^CODS`%`WU2qFW-vtn z`X5ye)XuAeE!R*|K~e*XMt{uZR8Z>L^tydA9b{@7_s5#;3zM#DS}~0QXs$YNYQH@f z4z6M)V>&8vyho5m?Y^u+b|yD_9<)WK|9tg|5(kSwEMpJ;Qr<%DD|Qk=#Pq{g8QhN_ zK|QLO&2xLHR0^)9}WBj4GPz^iFUa$@v%No)ZZL8 z+xj1q*c_HT;t;Yt-<_Fye0%!qo^fAVTstub!q)lEy>tO~7P>Zg)u6;>(PhcYFgvNpoOc9sQ{sb;Y9JFjlA|$&0FsEeu9Gqb+;5(WPQcy*#S8*wgYdr)}E_pE6 zY=d2vYlwy_7&6yBKH|zSz2h^OQBjfqGVa7}^$|pn7Xj^o>+yj%YyN(?u5{SFJF7r% z61&9M;5DKcq4k`)SZ)5`**&?*m-I>e zZ#6pd9~oepGkoC%^0;nX0x$O>S~DD4&29 zggZ~Lk_KFXos84%vS+|6WKUGE^;;@4zfsrb1wI_+hq|go&o=F_(~ysg@|tRit_R&o}Oaw zQ&Nz(S7(=yyi)wZPMH zJuL#m>76voxb&|cd$XmWR>~L6!AW4RpkwHaiLb%&Uz};Mj#(3F*qU{47+RTgtP@Iy z8^^Rf{a-|VQKfaFM#jeR`l@yRd_vBTL6h8d=1Uh4=k#AJ1>RpxPEM-T zPNwYs>4BH0Y5%JOg7q?&DR!b#MzAze3C9>f04C^K`Fu3DKrjY5go$%6T%I&T-A~Y+frPPLA4w#nQCAj!5@61?%Y%khveW+1qD6 zp6}kjzyA$V_1`P6Yh)L(6PWWgi`VPw>e^BE_E!W#1Bx@jw7WeQa?^}4%f4@T4NOG^ z?15^N*Ca^zOG8OqIt)rir|n>NEJciMe*yV;pF7n8J{zqzFt$9E zSQ4w8G`3qZ{2 zKwkC{)_l0OYOyEKLG0Ju5Tw$mMCl zrqAB`CTSmryX%oY%PJ^(Qs7ZN^y87atWjD7UPbX5*Sq`gIhb9?rc{gFl|KlLJcd-2 zFlMoY*7g#4?sxqve~e^iuEp!Ai0QHzzh|<{?~8Tde4amxl23>nv%Bb(WgP(xZO0&j z3dkJ9MI&*jpir8__?&Q@r6xw#8{0+{j>hgLo3?rZ-@@`Z z0v1fSq|lA&DHn!0Lf={()E6hz!WeIJ3#x_>+t%VFX)o4L!-l^JIKgS*@VEW4i-dWR|ox{z7__pJ#oyw_( zy1K0FvMf0l)o`*Z5%Q-W>OnnUz^@pi)KM=0Cm1U=g);bi@7pZMrm*w5?W+z)XJ;8p z(1c3B%ggIrY=7TFrZw`f?rXhy^Jd{=%5m>`;z$P$3@>~f_F3zayw~)SqC-2uMXuU) zbHoraz8HEoWfr!a@obbv|H^?5G*Fu@`d=)_+@9pz51Mcn-NxMDFJrDwTgI=~3`y)T zfp$1u$~@`Fy)*VBmMbQ2kyt$mp!4@|oSaf)szQwlxa1HxI`6JS`l`@u);v`574-JZUh%q`ix~ zhJQt=J-jlXa&YJ?iQ-kX3OHC(g*8U1q4hZC%J(kD#aT?)aRlwUd{i_S2?qxznm2xa zxcCZ6xn({(y zZ{!ffY3bY3aqeG(DMjZ+*0fK;__|++&Z@i|a{WofA4%ZuY!-2a?G&=@_(rkS5P$6Q zZB9Sf!e$6s{a`4`@|bM`(Vw@i^B=fk0IVwh@+dwq=Esj8u^SOw6wI+WpkM|AeLk9$b96s z3yKv@NPaItq4#V|a186(OoLX2PVxAtZa-7yT|-MwObCJi?qQ8P>uzxrL2NOlR;eOo-eAO*q$PaxxQBkSLJg8;bE+AZxgx{jfM^9J6t?C z<+RhD?aHeuTfQ+HndxT4kkhTLtyKqgNhQrCFq4#k-eQ~ti3!6lG(Ub!+vbCh;`bI_ zxVR%ZjS2m#Ni@YMc@+XV4hb`FO38ye8HD56#Xz>H>*THP!w-m1+wzKvHrM_6uLq9P zRm@_wV}!u(PkIWGWLi?AC!nT&Pz>%S4*IvV9^&&cD}TXAhe8bpvT0cP`aBMsOhE}R z-iW;S99X-#s9#wy#e;IzJk0W#>=1MO4-+ z3Q*Hs@!Yt$k=0{AOYK1@iQ@g{!qYldnU_YlKe+E;?@TaS)#zVs|r--Ia*g2?Rx)dREH-KPIbnGR_!?7M-&G>hBJIwebq|lc9$=8 z?`iMgFq|dre-#co%>o+5UWX!NN@lf?*80z$`Ioo0-o7w$(AxF%4FWpjmN_v$9x2aD zmc#nqQ3gc@IYx(6>Dhe`Cg==xcC_m<^JtJvk1ET=$e_Wq$0SC}J=D(%VB|3K=2ebt z{qM3^ib8xvwJJDI!(edJ_nM-t^$%_WLof$gPaiWn%6BOH@pUygmUl6EGah))e1JKv zgZTf99YezQ^?dT8^kEe*sM#<}6PfSv_jM4>@&S(rxuWZQU;=qF{<0?AFey}vI zsGn3*u#wPyl(>Bv(|)-#()DOKrjh|Y9`muDQ{MP_!TzGL?0*>H>ZJr+p_@YZYdK({ z3LGZ7yM60-ux|r8LQ_3GJlZJnVI{o*N{YzG2D3@fAm!C@SDF2cM}$wh3?(Joq&4*z z&=6(Y>D#S_y+oj`_6tRP{aH}$W927Yj4TOvaC}XCg=v{X(Mtz`KH!+x#w}=D-C^9ne!ug57&sTYySr#_ z0A1aDAfa`JuE8HMlFSGQ=^!>*`+IKsvb_$c^@oSlm65zolkpSebIrP!Kn670va0wftzuEeoLPG0NF!BH1_C^ul2=z_g zqCng>opT&=-z~QY?Ap-#?tU=VVX9fu`&-^{zt939BkPF!tGCeQRJL^x%?N&6)H6(B|X=X11HnM@+ta@9gN|-^#tGlkiKr6DLoy@* z8O(q+W9vOlErr~G9#P(Y#fRK(xxUe@6n2%SSg>I`x(10ZutdGSa0acsQojxqU(lE_OdaJcWpD2Az2A>qo@ce?7=qr*CHjtz;!>7EKpko*$V5W5WHu-#HW z@_q5JuUF=V+`~*P%`!|X2`?R&xz;Y@0)z&)+r4zogFAl%Bfpno1S)%-jw(SAAhl;k zDG!Bs)lG7j?kZ#W7_6)p^GoZg@MA%$5HnCUx)I-9u}`+9ghGsVTOC4sCd%&-ALWQ& z0X*8`o|L%O41|2XB!$G{0~2|v=mBe}q~w>Axb}|y!ORBM(CNoMr<+U8i!F~(s&5z- z-nI}eD?AmaH+=(6D8|43`qCNm6L(`Yma>}E$XGO%b9?+*5Kss+;ICywHm8q1Aa84I zgS>Z~4s&{7!UBXS%Ms^Y3FUNmwm0EDHOEOI39`np%6%lhe7I@n{LS};SI1j%KCcd&d928Hpsho9oQjzh*>iq zn7^@@MA1*7X;nChNAm&^=$YIf%=KoxhIlh|@UMV6W+iB#IKYEqaAHRNy~KwJJbLX` zUd3&j_nlb0Yy^*F;Ixi`vi=^O_9yW%Sd6HTK%IRnSxegc+xgxc z)f1M)FI%%}#K9v56DV^P6=wU#q3?qD+v*CI zJb$6eJ=KJCaaTVS6m%mdoPi&{2%Q_@rq@f}rGdC|4LGbNN z|7Kk0#mhGn&m_Z}4^IAtTOa6Z3~>YJ&{{JxGTaJN-gGSfS`Xmwi0)LCbBMJvX}uhq zuID6)v=ofBDUnoTrB=$}qY z#lXNY<#PHa8>P|SiU3r)K9zDqp*Sh@^+0mKp=6rXx{FhR|D}J;T?z^=vZm5B7af7zieT9&o_i*#sOdEV8o!UVlTwCa_q<$4sDJ1AXSR zS^=?Lh7q!OWJoNQ#AiO0PbgdJgPN2Mz6}`%5X}(=3wIJj@$hXmDX-SRr*I8A{}0cU znEY#5*D(JaNYu9}}7C5<5ZK zG6S|~MO75~&ZN3#ADc{_ceMIgWcfD#P!|+h6>86S-hD)jhL}9lNtk14rT({TQPkatn~hYpyldjNd{wKfeU($m#3*1D9vE zH)m8;y;mn=Y5W!5C!^MUCWu%}l)prcNW~+})(4*mQbnRmvBH^t*xgL*^hJY(x87#n zAq{n-l1#^4$yL8yz3<^hZ)o=EsX!dDWeJk__BUC?p@RpfzzN}ha8Rt50Cso`9{baCA3iA3^#-Q2Be00v0w&qoWxf;%MNTnBIfvbRAJrmx^1|Y= zyR0{b{6<$rEpHT2H(wi43MmiK;)Uc`|5UM~k5h0VP)>@gduZiku|>9GZrM&Vf^wswq`Wu8 zP4D9#``uj)N;;R_i9w^54i{N{F9c^q{H}%CE<35OBom0nVW+Hl>zZ@lO%zVQ*-ZC2 z7$O*P7+oQ7s=JQiP-|viH*?#&18f(^+4$A_&}luD>+bjKmdU@l4=0^86Qv@ z?5&3nzeMQqpZWfEx?|}eyfk6B*gz(s^}_u8R*ZT3^>S%h{;<1Oy4AZXuSJYHejCg* zqf16`yBE?W*|OcOrmFT>+aKXO!jY3G_GWc9!RctKYe%YhRvq}0nU%q5-89q`K&kbH z>?~pe++~Fk5fOX?53KR`^!UwFpJtx@ris$PtO_1zeaSVBnOzByI-PK(f@Z-(ckG5j z?)-P=hVrQ|T&>U7*EHZ3E5OPr_BeIwwaRGl z&DcnS%p&;cPMw6}hw8`%TwSZ`-~l>(qoaWKQd8Q6b2L_?1>SMX(qn80H%TFuB-K z`)AEef(&DE6gytw`BC)2)316`ESXn|i@0?wTlaa$IBtK%Ph=?4BeL^iR=LZMyU1>5IWgQ7T5d$ekMhQtS%C?VpbvzQR zfznC}2%LX^4~QwRW2*7GdtpXTlk$FVWR#^cHU#whL)L(a5O1>lfC(z5HL-WbI^iuJ zlLoe4BEp8xRbP@y=kq?%lIa!IsD-(hfnK8q`y}J(w_iNy6^!q+_++8gSgg^VUl=DQ z%RQV&!Vc`VLi>E~vU{QL$OPam2f@X^yU_T?x{;yb#XX}dw)}i`Xcj?s?@noLaNyMq zS9;I9vU24+`p{Ij>k5Lmt&uk#zwFE6`#wPGIT0P58UCBY zbVmYirmIe4#;{vWg!|BCo^W-39?FSzvO}xyS8dNmAq5$|NvVfaC+JBMg#By+bg>8g z91Q~P4W{bmJ5>MKG7$LyS%7eh7NTiL$zD{|+(q6>$AEi@M zGv^H@4(FE|`P|SgbmZ261NU8n7`dw`2Y$MvFME1C=V30{Yzj`)*#!<*8Zt=X`Eq)+ z;!6Q!+lZD8$efhfN1`6a!>^XGTwC~*>0s@KsD-%709lbzW2m&e=|`f=S4O%caF5is z>Nq{0DHkEK1uQ?P8-^moqWJiCvs7ePp`LWIN1FFXsre-FouB@wD&B~GKzdUBY^5w( zJ1i+Br4Tz$1aLv`qcw86OjNhNWk5coQ^o1QIQ0;cMV=gRLcN6iNTh5v$)k6+STS}w zmIWoz(3`>AHkhauq?=y^x9_m(wAMUU(@Iq zD&;au!#c0A2_mn(N_pGVQ4+ zA=4T|H|BAAB?xXGxz@8LfkH`YVLWF1l$+;1p3O9UABj_=xX>3YizYJPrC9uolt%hy z!hpDu192S2YVIv~)t2O8vN3=`IABxdz(*cHRFY)|HMyndzJDYIfC(d9_k@WY1veri z>~eZ6Zd0L_=5YzT5nT+oec@XgJxBDslplV}7?cxYDk?#$h?wVLG0(EeYkNg%o5`yi zgB7bEp-$RFWOJvpOq)SpHRki*^+45Zu|n$M2J6b!}}(+QMj? z8hAEzNBu_Ji)XSzw_`!)n4#Welhv(RHI7$Zu6go^iN4mGSbOgsxgljMXCiVsErXGd#>UwvB3q= zapn6_KufVk@~1D;D@CP$n2^&sl(YOu)J$q_QEYrAOk7Tm%$X!l+!X&|ytnF;2=^zw za}M_~_th&NJfshOGj<+xM|ecaJBcL4MqLe8U_JS@H(wZ=V3cm`?P4HeVr@NMd9c7p z>3i+QLPuTRGT+x5)mbIB%@-&jDtEfiido3D$rB?@LQ#^G_N|M{?j>1aWRzB_B%~Rm zD03J-;8}FS^H(IKc9{JqWPO5ID+mWb`MHieqa5n!L z+X;0o9H09uSzbAL`4__wwENi7(lWm>#W@X<_!BcEM4j~k{f!k6cm!Shxs2^1WGF4T zg2nF6a3Hl&&vv;wr59LT`uzsQK=%GQ4)WdsS=PBQAvWpW7LNP>)I?1`Y zC%6vD&@fN$$SIl$pIU#XY;BjyKy_W3Mx30so7fyRF0=I#tBQ%v)#f;**Mje@?DZxa zUI-gnPGwx7K(C8l7Lon2iwUK6Z) zeL-`l0Q=adNEY5vFn-U@mkm0K=BJ{vjW`dB9I%kwq8znr)g+5{J3NaD8(@;7$5PwQ zjN>m%v_Huy^Q6?wa8u6eW+ost7&J+_B|i@nY-z7Wc)T7?Fc#fl*bWiolY75*Vzsy8 z6hoR|{Vt8q?xOVHZm?34gjyaxynH8;dap3PlbYwNAw+b12T#PZoqpD~D%IhD z-oT5TuX_*L$|$o0P9Bk7jxbba&=* zJ#hkxEvpw*Lq?wlgQjls#;cXXi4f~}3Ob**fk?Xffi#SP^qWs)yf_#3BkxJI$wJ5l z(G2D{l(nZDL8(@c*eWXm8iY}0|UIT0TAR%d{SEKLo-L!%>yxK zEFiIU9J98@k9aCRjk}S24XdF;swz!Rb2Cw&`6RW(?uhu*>GnKy1zi}fP#ih*1;3!y zU-P7CVLqXF80qJ%7%4Br%MwF-6X5D{FEWX*Z>w&9NgUg=XU{PTlX z+I^=RNXm~g6>J<&`{28e%pi}Ol{JMuagU9jyjR@#r5nlI@+-qV@7fZyiLoSC^5U@6 zv4#+o1t(&SZwspv8jOKGqffRW?Plg2S3_r-a=_QVn>TNE=k3}=w?6jJY_i@16&T-x z+ob7nblAg8{Dw){d0#@EEcL?Nv9xZNOZHwbnS)+GdG?dc-f@6+3mpemW$oKsY_eNg zy^*ysI-{}z`7&Ds;1fH8J7?F5k*%a+IlXlDK`z1jJ#M^M)pDnePeK^kGoMN#cTgcx zO}B_%SqE>9HJXWM7cx1rSn!+#;HJ!VXfb?RSlH$aQ`UFpO13tc=Mx0D!RCU3f^nWp zgO`xPf)#g9NrS?o{$+JG$w1v@UeB2<##lOz6>%lzC5rM=?bXw^Q{Rse-N#YfkeFuD z$^%7YTtre5A215BB7j6=<$$!w?bN}!F&4Jf^Fb_>$mhE*FuZnWs~hUQP#%WTry3aE zZvYh!Wb{u}Hto&#v_O@GrP`G#Ar{YtFFNNNCl{UGoSnMV1WxLdYxEtTCQf(LYY#p_r*s~RdaFrId?iMJo%jS9@@jdSka|g!0E^!d8u`ubLdfq{ zl9RQZdo~J`zv2avkvaF z6SFG)zysAOC%|uOH-hRl+V7VVWp|P!hab&CQ|2?dvTrZeo;U}cmxOtIL!Nw=MZ48T z1fy8l7~6DV6!9sqHfl9wVQ%hvwM|n@#|r?^nylDTihN4HNTlH!JPRT-^g+s30q-|t zXD&NiB8dB`TT16bNKbbSZQluzC-Zw4mHpo7X8nsmkBE;4<}pr=dLrstry8TkLIFxh z;dsc}bdJTyeanX$T!8cNSx-b1Y@tL0)^`3dJrw1AvTrtE5V1BxIXw(&LJT!qtp6~#Eb-rUZ6wEMj};@p$_t?#W*5LK5EOZPsoz&WO*q=;=0;QrRG zdsK<=)zpCN_ag-3sbXx5KF-djXLLSv(Ssy#TW-or;x)AFpH^}P9Mp8^V;@N)pT+M^ zBqiN2QXZsLdvYV=n^2S*KiwC%k@ES)gT_h@%>b48HK2(Lu_mCFy85k9b>14#HwM!y zvu5fBCxjyO`}9A*LhBJt)voiUh^;HiN#{vT8m;ypX+5+16ZW_mcEL?^$vTwu)tiO; z=jrtWI%?)C$3I(p^{A5u&p~$R^9veJprC=Hl{4^DKBQuKJY^R-TzQxPP*y>cOK& zkH#L|PaG~kkrE;rF5eM>rPIBNsVJRfQ9{OTZ;rp?sP8c~)0BQQ)trjMjzo}KVHJJP zCa0K#+i>~-q=9mc2Y@&7aaZ83UWnGopk?i#_MZak$rRE#hA*j~*5MUex`}*FSF3+e zdU@$ceauYc%LQ~KRxo?6d8X&<=T;s!iVWX6=NYwUsk~YY@&}d@VInx^ZC$)}>!QTD zQ|&1tPLTL`E#Y-%PYFv!ZVuz1yNiyV^9SLYqqIC@xjI@>yvD@09-a(8R+!NI4n-89 zZPj!qv-VzS4YM}K}lFR zxZDY;MO=4^i%%W}XRK#cxfa6kl1ly;OIOK(WoHBwbp_}rq@CBtK9f3nt53+wPoJm$ zuud)ANVzD$=7p9+VN>Hb-44E(O*(EO!kaw~-dKK6{^W^uZkZnu8U0~yVx{6>5$Wwr z3RAC^8Fh1BURm!|C7W7H=dj+TH>cb-=gTl|M@g~!*1n6_D^WJZ8C{p3UtU|93B}Wd zu4)dN9uGWvG@Vm5WHSSVAD}YHu|1EGy~4*$o;^4)#7;T6s6n&)xP;IsDfd+Y&u0<0 zZc;g7S+3NC_#BJB8lFUdD0|i1IgsyE%0)mB-9@wiThG;zC#Sm$sU5?fBHIx2^YcQ! zK0c$j%Zw|T1kcEQ-+#4?#rw-u&m)7pA6eTzC_bYr?~%fASCnj}T4zrcU7NCadXOTT zHRj<4R6NywBLp0i0-nvy%{>Glj0C;}#kbLrrKt(M=cT=kNy0`IA6-jocFSdRNrN^$ z>pH3Rl_6EV^BP2!mgZp_*Z211GDdOhb&-kk=sKwt19gS>?|=FNCRakv$H?P4Hx1HB zU?mJDr%ZyST6qpqY$ zDc(l1EBSqW_wL^x^;xK#3E860T74#Sts}o|puOCEz-sRDWje54CU8=11|lpiZ$!Au z-TAPX`sp)fUS85h{rp9HD#tv`4akbh>>a(p&)XdW2q>IXXw;tcm)m?ii)(jLqblBF zQN~E{fc2 zc6)?Xq2Oa-DazEIJT(LZa*|`DgEQQ$zW0x{POiH^YmujS@w z*6sSbw_dbh8)=F#$fPh)(=vQlX{DRDcBX?F(06?Sxe59%2tkeg$D z{Av6~*L2bTZY175SZ^@}i6!Lz(a3S7ku({kovu_wAlu$s)9vWeHhVRlVC1JDYvHhq z_d3iR^*)s5@e;I;3P`-0OHg{B_B7j>{N0T(vJ(5}bXEB6jYKy{(J;K9aJ`&*~14)*cnu*R0ricb7$$p9()KcMf-%C&L-@ur!`h6j^wjX-Ro)Y>X3E zB-7!>Pqm3+>^ww1mhuw8p+YC;sY1eh3uUS38tcoh-19o@d-Qd%lx!51fjqi+^OKY6 zF{#lp&ure)2)b;5zw;R>FKm9Zx>sVn*82I)OofWV3c4xDRXSydLp@qpDMZ0-u_I@s zw4Y06b zL{M!NR)z2GV{WY!ru`Ffou&p2kM2u%T$98v5OPJ8)rFB@U@1z;aza_13uKOl+P=Tl z(7Z4Ag(&Ni4J(WPyop)xr$Pknp}KCK(KSx_KuPBbsOefOXFs?_G zNU&%;p<+Ms(RVkAp6*Av6Q^l!j~g2;R`fWE-v30Up3En9xpCqGh}+z%>gsVo?LNA1 zbjvfFN0lh93EXl9AniguM*I#ulBe_&t`fsBFyyY=2}MLbY*n<6vkVFCzI*kAJMJpJuNw+Du%^)f_>cnu5l`6t?Yn=LKg5m`p`b(N=efiLY%GqZJO z=o?aSuE%K#GpuVuesr|ntvM4!8@Cz-OMZUZ_SoFSO(}}Tk{hwl;?!g{>2pm)Q!+>rM87shbvi$12<2mmH|u*gRaE2raQ=4rEi}LGox#ZU zz7jFlQAmc)`t<1&`(@?x$JI_Uu@mAO_TJu0&F1YQ0b+G>znd@ z=Pqm6Boh{grewpZJ6nGt*=rrbWPptlbnXk>r9+$LYiVlgIS3)rwZ)}w)p4%N@yzY) zyMuayX=wo)y+bPg0n~11$*rzW$tALKH&@u`Qt0SIK2}ki8mB}3c4^pTU&U1R?&Iqr zvIE~q)a)Zud^V-X01?!Yf=7jDVo-CyhRZAdERmG!fEWSJ>LAq0hTcjsm=zI38M)l@ z>7{GCaK`g+uUkY9f-KrI5R!+2g^+t!fE`BZT<_$oE@ zl!ik`PLu_&ER0b>RSjhO1zz?q2vbM7mlHSFKc61fnnm-AIyd0trH7r~78P@sNAu#a zipT?s5_!iM$)it!t@*gCtBbF;q%Cgcf^nLZP5Hn%6{%Hs19Y3^fqgu<)r(TwZ$)wO zyC|M1&Wq$^hSep#a0@5CFFx11@&2Bv70N*KF-kP%!j+{98(LA=ZbI_i&B=vAqc32J zuz)Tv$-uzy)aYGjGriZBbVjivtOdFMp7P18qWz{NIepg{Jbj4bxR0ulQ`My?)>R>* zFK6e_6;QOwC|xyogudp41U$=T<{7%-w?sBtYzW5|utjP)$fGas-7fXnjiX}`&}Anc zPl@Cn68V_ZclE0s5&DvpE_V*?{qd-(YCNioJS_U`M#InuLOb291CY}sP5>_A*@}(b zY~W8Ux3I9-te5LLp@HFlpz;Z=Qtf_8_2r)2UR%8cCjA2!(_}E32*t;D(MRG%eDnHz zPSM{glV{tttN6M~@VNsdr0p*8mPDRozJoSzo(2f{`S@g#tMR{GKPYEu@+_%V#vpez}@ihA8^m*A!q{!TW-KR%AwC3GqLzOdU z1|g@k5hBrpS5s3%j$(R?eb?nCo&;)~t+~hCvpd87Do0RXeRG+^?e7IE0#dTz0565u zz)be!!oA6sxIGAff)a(-Svo??yu?+3#$nO)LsF)Gn?j~%+Gu;s_YsTf=LPnD>%h4T zd^oW|ZI!y8g6Pu+q5{)48%F}TzcsY`(w*IF>}}k1i;s-9>rSs`c2HC zaL?CiAsSM-jVX#%LqzJciOiHF9pKTCSO*`Df^928D&j^M^v9)hfq`{)M^jZ@_;}T@ z29DiLFHhqsEhc>LPCl~?b#c6_p|~E*PG$>1BK7X~E16ayy=P8F(#(A7k?Sgh)E#A4 zAmtK37HmX7O4kS=U5FBe*Ee^5x^%*>aWjaghsEq{wP;-b-OWV<61{p6y;SwItBj-X zni#U4)mc^3_RwL&c+ft#GzvQzFzEN7jvZ(Pb3Fr$?$Z_3u9i}~MdM&?yY9}Hpo(o` zoPABsB%G!~`Y4YjV(ch~E-kkOy30f*e)-TWW0jq35>&Qq5CFV6et;;barc;m^U=3o zj?J9R8`G84c~$}2SeHSBFiH}1reigWK8oNMGm`xF*43_kf~nVs?*Liuo`>EpZf;LY zii*VNy_4>-c#(vqe}TG*;Ht{XwM~162XAdwYi{qIGm<*WdNFYMv@69oxdFS4tWe^6 zg+lIuyc+uY&s(6SHxeM#X>#E%kGhjnAw;389uyS3-%e>)MwxnQK5VpRvwFCPAsi}S z?Mv;??vg@KRme^DAIeXAzZCgAhfCi$Xgm&6?#=}Qec;aD5G8cc8Q^}60?pr*uJtw< z1dHCv#7FSPSH9c5s&gQ+2W@C2q8a+|Y59luC5I61_;W1nqjgsdVCB>89c8T}8u2|C^ zz49czBs)h>C|+F0@Z#0s@~x}ZTOW^{4qd4pFOzDNdP^DBJ+lu0z^6*In)k=?r@85N z8zUTIInUocXiO@ruCCV7f0RD#c}~Ud*;UNCd~IUt%r>!_TGBy1S;ja$6~HJHyFBCX2Lr;5=qldESfBcO#S$zcDnZ7<*!qOSqVWYIEOw4gCfDja*R!v>G|j zC;OSZuWpVAOij=1#lGY`F> zn+?)UjWiJQxBa>MUQ;$iimvf%czb*E&;~QLxtWHhNcG_IZ%NT3sG?h~)=O^R$4I;= zd1{JZj_2)?FMMU441*!R?gZa>~B=*z47c$GrmwS}*p7cS}BK^l}KXH`n2)Hv{dHFM&nQma-l^ z6UtDKv}&cu&UvrQSi{7(&nS9U`+NFKgV=*`Vk+kd0mb?H_^V6hk;rew=g3Omebvo2T<-0wwZ5yeo9otYTzndBzt(H*UD-Ccdn1|^;-|?+%Co1BAyMsZe2BT zW#$&J6cuim@Szk#Xdq1My%?Ks%Tr-^aF>m2S8r?qhDhiXr1#%r@4Kj4FAXgKD?AvN zi;0%)6;pEU>f=)-Iig*(RDGLh@0DlP$neEt_o0C9u9CoWXRO}3*6~>pzeG)Ob?tYi zj?N}lzx!>v5vi6;b$QpG0#LQ?M8rnP(tG*c^t=xFIg5aBeeTPi!Q-;FL3VtNh|Ouq zP_Mf6kN1QMK2t_4o;9mlMe7Yow}iCdMB`&(7j&Fwmc`m})5%z~D*mPx3isfO{90D@ z4Al#nOC;O~bHO-{oQIMFOp`sll5!(v^DW^=vlu!Ue9B5ogEoq*7w&Q_bO40c5^HWU*a3P>CEY_Y<|m_+=|oGBA&2Z z09BIlbt|Yq@Ov4$y_7|3c0hRM21iI8KIPqdfXuoYMh$tjFq6DLwIm9aY_L&agVgJY zh^b!)-5>Ub>K+oyuWe{2_+sVry}NhU4FPMoI@Q7Ju6oi7J5H`*Lj~u@Up|GhY+Q7= zHaFLp^jz(PB1aRUk&{tR`iTfec77Vn+wuKQO2 z_`K!=U`?zoLEQ3c|IJYV`coM7B-(l>qvskYph1vYOsdR8QgP9E^z0F35lJGnE; zi0!aiPGIvK&Oyn?)<$zEvg42zX`}qLj_>`Z!YS7ZNT5D60RZb6q2eVAefc~QJp%(v z)G>emw+Hi^Z~Hps@EK96N70K0r&&0?<=7Wtp<-23Cd5K{a(Up`=`m{V!t`*Z8gvDy z1v4>ClLBgw6jF)xgdC6izBR9CNw_39ujqyM`LsU*EfQY8@%dKZck;p)S>-wI^~NRc zFG)*60G(y6fh+ck@m?3rqeq7m^HL7;e!)IR=sT4^E^ckvf5|-#i;G0uE}d>{pqA~S zpwGH3jF#bcfYgrRRu2E;FZL06zeWJYk2rO-#uf7idj#@2BEMcyA)Z}!EDI$;(z0Dj z+>a^m>sWRvDgs~3_1J1_YPnIU20jHK-FR8b-2KT}TJ({O2+WY_*?aq?>k z%Ds6~om`jU+)9d9ZfR{;00CQ+P01B;GIY!LF+j?_wQN77A;f@i&UPLMV(7eiy==;m zLT4oKG*89*B8EGHTe!s5rEbW%VT3D5dF3GnYV2CWp~v6FofQ0!G&FWkdY?xpL>my&QdEUzCCf4+$P{6i0#7k4D0kF`0IOA8D~ zVacNtDnVm7W2Go3M5X5M|D+NUI!vUOPTstwUWM=UJd_Y|RJQ&6Mj`zT#PUFrr}niFze|?>P1}F~oOUT)j1lMnAvZVn@i?5P_VHR!aZzPbTm3kRLvNyemU#r&HyZ zfe7tVZ2L$qEZ@I3mIduhk#M*|%X4adzZN%3dsS+w?6k-TDIk%@O$hEkyxfJ+%9 z^fRC1f%9b*U)x2GtOwPK-+8TFmik5KG)oLh&gsbH#cZ$R+O*_R1|Ko;QwbIXvs>vN zebN;Y9BA%S5E2uj$@r>^&vo|8!g{>C=_^m!L>&E1q&fn53}J+t^gnWIRuzwS;h4TQ z7iFW#gN9804G1MBUj-ysF5=@*;C~8$t{yap7^l^;eSa(IO2sS8fOeWGIP)}a*&jTJIZ9#A7#S=+AEZ4Sh)hAJ+-pRZqdhvUZ3aZwE?zw&cDFrR%s~% z0>bEU0sIfuS*=syun^7+1O4%b?$;@s!cxvWSUP_NJy+*BQcjj2$U}?@m*=_sV4lO8 zvgeN6$W3sWfCUexaoCvP4$e<|-&}lEBcCAJF^X``;clxJnU1dT^0%|nl!!|E0vQK~ zkgIL4T#RA?t{#?t0dSEHeF#t3_lF`;$q{CUk^b73_@s%?JA0~%r!i=-y@|arPOY}vy{l*$}^BixonUBj8`n#khwua77{ zQa^g$sY~gP|3m|KXHoFTtSc$;)G&X~rI>NcH5<2SfeG~+4Ydt7?e{3H+oogLeI@g< z@-myERmhE=d^veEFIGw|um7WhjBFaE6u|i~W=kFTZtJK64$;cc)h4bp2~3#>NwNzI zTbnx_z;*JKw^eRij=>;NQ82Je)%KgaCGov{kvaDz7K0?aw%iW1A-#Nzm@qBLFv_6d zMJEoC;f6I#2_zHH`RK(FoNOU^tXynn6#>xp`gALV#|Au(+oDbOEBC`diLSP1y?uy2$;L0XOP$ zH1A8&uiVMq#S+=I>$DGP535;EBZ_B?jRQe}mA*TQ(k|#wGLFY3O;nm11i)&GG#;l< zci*{AXb!L&KUjo1NwrCtDQ-xW7&>l|B+4lua!f;SgoVoszKOh{K%yCG#7F*lIi?3| z6PtV^b)ZOH3?ay{i$te#5>t;=$0mJh;J)=0P*SR3ISp7K!wx|}z&YjYyy{csr(-4( z^q7W7pKpW=alhrG>m5j#B2`E(8$WC?|I&)|s=1BhVMM9b?n+TV?~#uFn+{d)7&8H)-B%5ps&vZZ}^Du_V@QkLTP4r zE8j>tELpi0RLi1iis9j>O^>l*&==9&57m#pheoi5Bo$lIvB2&*FUixQAY|8}=Jo&FUCbeg#00PizY+&jo_MUdbB8WQ&||5NM7!&VMZE zQpqp%dj1SAQok`Q%zIpP_ijN-|4>Q+Se6R%OAg3*ujl#mR_wluC=eFn=E!tFCF=|h zeCKwh!Dj_5E_b>C5Y2nh;tF1(19gUK$@^w(-;?YZYcz0ugA1bv0e=s>yk3)$PtM&^(w6qjN!giU*PLvO(4z}&>MDHPjPZ16FgLH7P` zrDiq+l8GL2#M)$1?xdT#VJe8fceGHw4t{xCIG_AT@$q!+6OV}4U`-si5kbcn!g(S_ zM=Zt;I+mLAlibH)?mp(5e{F7Xr}Yw>6P17HJ6;GQRojgVWe{T&%UF&z?R6dIw5_+p zRG{a@H&iChc2bJu_l}Ltvo372?1tCocBM%6I7$z5yB6WYA3Q7B z@n{j&PO^V{yp7KgEaW@La}j|J=f_;-V%(#Ys*iCa(scsTcwGm3a5jd9D#`u%HR(zKWzWH!+Q4&0Rvz<@ryAZaT zwa1Q{9wpx+r4+9yM8#dkc?;Xv-`i^@1Is7D3U7iqYwIjigSEag+5IQ$rE$Y<3!tV;~7j0#5#m){tW*1U3Q*er!+IGcjgCB(^r0x0_b^?WH5}I!;^i?ST)L z{!^_=3FC`71ZO=rDvsrbRYUt3lp3Wa&N-ogNC_ zvc<>Ye01c*#BtnZz$EpBB_Ujfbgu&lY)-T>UESagp%3H8tDO-K{x07ctEgU+XyOtA(BWZ+$e`4P1C$@uGA?MXLJU-l> zl1e0^e{q8W=PVcHK58|7kvbpwLEZHnDx5f*KUYY2aigfqa+v?56K6yb zK}WtI)xfkXnS*WdO=7VQZX>2NiqlcY#)b#NTbH(z^Y9G#*s<2949 zF#2fNT5yJ`nsnA6*x`&v@0qEgN^haYNzad40CyKI!g+q&gzb_^N86-`ZBp_8(?i{VR7-TvjBMUVij>F0)s{nGWRkL0i3VUE$J`$4a;|( zDG>bG*|b5Y8RfUWS;cR3t}VV$VV9UC5spfd?^)gq?OE;K=y_sir;`o}B&>cMv`N4q z)ig-IjKk(qI5j4DYcDa$409!A_zLAm+2qxYmAf~U&zH7#y&FXcscJaYS9(@oxv12< zkncY7HJp@l)!opr!{`0GAnU@_ikA1-DM)|rQOIG}Wn|7VwZf5EriQNsif_94i=OnD z?Gg@%i!(iZ_^|)i=R&00>w|TUCEA=^d#NEmt7+83pA8|%EBNuj_*4)^EY9+>Jr6Nk zACBjMykW<%tRpY1$8Fbd-4?-rF?>XD^;v>VhT|Y}?PByWAdB)L3Ajk=F*Z-nTdc&y z2xpcv{8;4Lld$l& zVa&#BT{>#j%|$wZMAv$hesa`^d)w*4A)maV?iZvYj{dz*@ZOA!jCQdbRZDFMaC>qS zz+qC%{b+knMyifkNM147R2lQ|@BcR2?`_!hJ4r4qCC+^u&o94rjbLn-TynnFW5YXqi4!#Xv`GBB@8?d#6jJp1$>Zl!VNEWDHx7SXS~F%-@EEl| z(0}|ii%v)Vce_m@4J`wM;ST`)!3Do02C0lU0#=*ogJo~TR2*M|=aEB)I8?!}#1^bF zzPn%USTvT2q;uhIt(8WcAb7gYXlr}yqQxQ*h|l_3>K4r=CnN@20cG7Jptx>(eX=%1 zd07ZB(Il9~ETskkkDZXIGyo}MPNNHEx1cums7<#UkS3HIHHSi8=u2yEHf#TnNhojW;(phx%5J4R*pxzpue_yvO#M zHy=E7uDIHHy8UV~fc*?U3E4V#%_Tz_klWi}S|G~Wd?&;QD?PmM%(CU&h=b*1QaM9b zZC1d05(I7YEv?{=Q}<1d40(4e&$rLcCleAW18hwQ&L=`L`;Y&m%@^@V;W0CPlA4d> zKsrKS+gPhu0~a9-$6Uvk3n|J;-Qb??l?#Kb~NOM3!?!Q_#WlD@D zW3gCsdU|?-82`8V$ji&4czJpE(9qBX0lm+FP6E9XKz9=v8JQ2zECnlG{M+{h91e#9 zT91Is;~f%-!~=u>*a(0B+~D`uTwGkb@cHd0ENW_MTCiO&6A=+@{G@Lu?f>!J2BGf* z>?ha1O{f0{LWG5di6EgM7==RpXosD=|EptYuT^L}lYh9)Z}lfPH#Zf~(eYRG{nd9M z54u4%vi?>?{uf>r`ZWQe|2XvZ&7Wi7ujt?T9rP1CY-=!22>ury@h^9ZoSYmQcz=KA zSl>zCUmX+9g*ovl*rlZZas>T1UPw_HHXMa{gW|vO`2Q=H!aR2v9=x@a9uyG~(2T;P(NuUeF%6ywMWFdl^WZk<2+>MP zO8-~h`+wr06ciM`zm9w458j(-GvQkvD&k+(?Zr3V+k@BGNB;}|e_jLnaxJt+-p&nl zsysjt_+?X2Q26B>!uf>n{_#BMkAFJvukN?=c|VW;E9b%e^I;r+-^qKhz463oN<7Ez zEWD`dSG<_oH$0zI1)h|Y^%t56*Fbx{+q-w~Z`bGls_%ep2i=~iWoKKUpe&sdQ!0G=63Rpk&Xo4 zU!{Z}Y1;qC*#F7@@>_BsC;zMm?7aSWJ4V8s&&(6}gYQ4b|IR%NZy50Y?=%zm54O+M zziQ9l?K>VG9+(O-pLg;MONGYwR4D$5_hT>@)ZLfElamqsF&1`S_q!ew_{qwD@t^Xa znI{;JggNmieT4I2++@Muzx@aFB~nUC%2^=f5Bi9SQTWa>g+KA1AOoI1Qp97aiT^m4 za2>SAI@pgF;6CSeZZNN$+qv!h?dS2%-+vze{B7s{=WjdrJAeOqyz}>;$3K7jxd&UP znZU!JG!y23HsOqa%6~>KU*P}W+lO#1Cnsm(Z_j)n0J2#~K$cDXY>S`!wukcgv1fml z|Gm{pct(2CKiZCPKKKE?1Kb1`9RC&{c;BR7`H#YLix>Y>{xfi#2A${c{0AcOP?PNc zTM+x7yrinCDlv@RVFFD%x4JuWF#i9{|6z~;y9FqIITzY<1$;=qjUNc~o!p(YqCmIf zmuvke{NKXUvD*Ae{(}#|@jq$W-{NP8djR`T%{$wJaD4xo6!3rFpXLC9O>oG7=?DLJ z$i!`ssVct%!H@t!pm${F_$MMF!wV@6{w4njb|4Ld!JqgK{&L0Nf!_b@9U*}U0qyaa z!1JA3YK+LAcu$!B$G|2C@#OnkENI6y;2ZxfkO_=|)w*6gx2R$ilXL}IFb=VoczvTZ2%n5}l;xOm`EgtyuI?%1U zPoF+*4tawBi{+#SK4DeZRP62T3EO_?XZs0z7=QasO-=WM|71V-V1Mih$Nyj3e|8?k zWPaS2aBsphAghqD|MBdTCr_$4Iy$O4J3Fhpxw+}V^0~2uciuVvNy+%}1U~Py`F;69rho{{#o#OQo()xEj}>+n*kxejDIs-=D(Ex1R2m&EDtsy`j-o$p7#%NG1T1YghJe1C-f5C0$Rxuwc~#61W;(Vy3VtQ!yz5cs!=0YNOE1=?og zCw+wR&%{8AfA#cN|L;#9&~U>(JUc7qx8wg$`hM;SbQ0`(4)J@y`(OD_=mTQ#9jm~3 zJOb>!9l8!4{N?4EnwoH%e~%BuUnn8Z<&T^X0QG#O$4p+ z#~Aq?jtTOB2t|SyQF{HS@lWUv1ew6V=JF?+K!@=C_u%~Br~n%Px-?PSPx(j~6DWxN z26^!QCI1O>ATL=0+V1Z@(cgjJ|M-pszYi%HCtm!=-2dnCFRs3cC#Bf^3;&^wwjt+1 z^52R7;JZOUOf%{y{|W6xh=ZHz6LbL3`COC|whFkW0{;H7CmiqG2;cwQ{@Hmye~10f`x2f*cpl-oHSi8~ z@IFmI-ypoxFT5n=GqCW4|1$6I)B%L{JO>%~YafSu4S~GGz&rh0eLx40g!cNQeF(?Y z6vX&`i2c9$hd3aiKa4c*!|Uv4{13bM@7IAHPz*~?qhw@ckdR~Zb3@3=$|9ttr4f>n zl0P>19U&$rhJaW+0(?*iLLVe-(6$IQHMKu&5O4qE9Kx}(vhpAIk&uu;NJ&W{2z$aa z2+!K_Y$W`Kf_rKaXxd5R581Ce_fPrHCYbQc`M_{I9Ua|$uyOvacuyb(_(BU~_XB~( zKQ@@(uS5vr$Nysdk3gR&$^2U^cxR3bjIlLnBO$^)|5ZMbk&y(jq(FF|ztaZC zqaoOVf0vPjHUeATyRx$K|K#iax9$aHd@L{z%>ShAhu#nG$u4pe`2SbD-@*$kyaZm8 z|Kj%}_>Ca$^V|5j|92Y=eE6n2Pc4 z`~3f@^I=X3`-3i!X$0Gb6vK`eSN-;foxgM36LLTZF`s|5Z$O6Br=_Jq_xbnyz??}5 ze*8B;-48kW!wv+UDbVeo*#Z0Uolg|-v{({668wQbeWd>(C++?fS_#HJ1?X&l`1=XA z4I$r$fz27_{msARzl8c4o{UoLm$`z_Ccyuwe+8QUC*J}505^Y*mA}bJ6kdHF-GA5t zzs3UzzTgpv5uJoNf^f~x90|fPwD|7 z2akmuaRT3WUiYtdOn5(nAD@oZ8pi#`pZG5c{vI*#`(YUm{D~Hji{<}+sDFg{4F&li zo&U%?Lk-5i%m*Rxf22_vZ>GomON`_num4y0n_#DqkTBx~l!kuz_IA$sgk!?IFQhb# zCnjO|h5!Gnz4L&ts>uF$UV0-5frJDIX%Gm6-fKiGtX;&8eN|TfmbI*F1>35-y6U=X z-L*GXkzG+ySS6ynu3KztWi7F-6-7~sklg?GH}}nb_rAOb2_&KAem?VVdH2qoIp@ro zGiT16fo~enX^)}~+rPott2s%kObKw2zEuga_LG^9LPtP|9{ed@6}8K=T3hs=Jca17!1m=h;cl z+fjbO@)Of|@PtEqqFZBQi_s5={_VvBvCCrZR%SW#{GT?F`&JJ08~2`*n%2$Zds~0> zzT>&xt`7X$uCw%V=^rxf+g%qB)B~+Ncmbc`iW0`mQS@hic{TH;$8p{h8^iACWJvZXPe3P{e!1q6Fl3UDJw!N4TJ8B^`_=xhn_8>mk zeWya&e^YyqwFg}G^D5Q{G}QwcgWt4x@!~cIU$G0|nYaD)PevwX=8RxX*9x%!n;=Di8YJ6|~Wb%_(wG^+|g!93{7ZIF@jv;5#=zM1>J|Hg5qh-N(<^_hLF9yeh%7b?KOwXP@ zQ(Fp0bKa+aNq}DufX_TX7SUd*pzO>za^JQ6R*iXux$N`+&*(e)l5O7JvmxBlzy3Tz zf7f3oB`MLlm{UBaX5Ayzm zj6FJPem6Cv0-f)(nDp0JU$(yN&b6`n{f~)>w%zsn(S)h2tjwa_zU0TN`hni1+0Ls^ z#g+l$fj8>LJL86Tc6~&AenRlT@{*F09mubDYbA8FKFIyY{4nY|M899!|DL$|{vd2X zy$0M1jLF8|!G15ECB_3AMeNU`?faNDYgPjOPKO95V8TlpOP_XJne#S&0I~u5<$%VA z@9_N3w+?rbkrg2y{gnrKt>gTI64&Nz?>F{LK8<{9;8VMo%*ii4(5&gTdS3Lo|D-QW ztOwQ246sPS_}`By_gy|{aJuw3)|F2_*u+QZ65Eb*=SESy=|`-07(ko<-p>QF^9-rZ zao$)=`;W2-86H3nczy9$XKHPM^nY)Ds4eMld>50_Sh1d%+2Z_k#XS=Lhf?e40J!avp#D@uYFL|TpW4%zGb@N06SN8VbwTmRu9FH)pKE4~Pl8_TEBsT%z{^E>#e_-wo?A6{CH z-$Sphksg}^W@3iqL4(&scSFChPgPcCiu1=fYlbWzsGsOwm=TKy8t|_&gs$j2Dh9r&)Ra)_vf=-A({J*qlFm2j(1Xk9^h8}`(DJ2 zeC?^|IpPQ}Xk0Msl*%S;QS`*l@E+?mD*d3amOI4U(YY$iU!LM~W@h$rswaF5?PIrn z(Z77?BI0>=3djud*L=h~)2C10X4Y~0an>H!(v_jti^8Q%X8etAbv|ncmuj9E-7i`W z$Y11*x^d3V<2uJ251?N>hRrEkbznQD1myD}$c6^5jIe!AFyRIG3ME=`;Kx0DjKvj9R$2&mb z^#{zo{gyZq8Q9{+GB#Z9Z}-yOQ;6BpQ!%@y#E#pbc3(4jrIXi9eSR>GFL^b>V^{9G z@4j?F9ml3%K=T9Oa&LHG75R9yRNr3KBg4cI)mkx2cmG*D@a&&PIz5ZhP5ZR^fX#=w z_dDpH1HK&kFOm70!qT&l`Rg3{>_+SRu8+?;r>OEW?7Q?CVSV5BAISYBtTpVg?{5m> zec$f4-+mes-k^46X{WRBEx+M6SVQWkyS^TxY=$Ugo7jt1KOMQl9V(dYYU*4mfMpX|&yZk46I=oVtlOM6p#TS~Hs!;E*o~f0 z9DvrYZ%2MjF@T+B7W8iv4+#H?ovpEYRav(4k4Hus95L+s<*?@Mu*d`d5J$rTnV}hfhiBBI?JH=NA9(erD zVX^xF=>m)u-o%gmkiEN0mV5LSoh3^Wu>mU|YcCMpmFRcs=zq4UJ!;#8IPH`ve)LI< z34STsh;}yVzN{q6(c1CYGC+Q%6Py=WS2|@v*Cukn=Hox3cYcKLc7L9A99s|u@ZHG5 z_V!RT4*;Swa!4-w4aK$*TYd2PKaF;V49H_l5cJ#A_ckFTRx)q2Q-=@V7DRX-DEMv_ zVTXC%(#xLZ6T179Lg(d$@pu4V66C<5dqy~AJr$EJLUWr3I>wsn>gq#$#dTb6VHiLz z7efCg`z=c*s9m2qvz&2ZJRWf8if+5A#_2>{QrVL%?RAF!KVW}80DYswzPp9dUEr|) zLl*0E?~5h>Wfuq~LPKbJ@fm$wzY(MVyEOOf2C@;}aLE8CBP|$vSUiCIUyc0Vr9=L2 zSqy+3AQ>CnFJjSNbhAl(kdc<+{Qj2%jo*-;?*41_1!tdFZq}a2x5Iv~Gxmm0@FT6} zy5raaFmTBwmn7iBF%#N*z5(jLHD*gn&I{=D(gS4k&Cg477Th&#J+=Y#0qlp<*@r4A z(b@>zv7dO1F~IZ0md@V-s=wWXfIOZ8O;&>lubs+{o5G$tl|!CzI(OU8^!pt--^ zWm(RWx!Qv;{(RwUtUn)KqkY;Fu?0lz1Ii0MrJ|W(|B9;dd zX|vslt*-qQ2GxGgnysD8+<=|C5&g8TaMX|-=Y_}Fhd7QdAltx8j2Zecuc(;5Hh+Cj z(moBpI^u{UHX{bM-=gu^PLV&5rHRgx?P~j-yB~^gW@HYpX404J!%@~`Tqk{C`flt& zVb>Pha}+;3IcK<&n;neTs`B6g#ck{u6SgxOvY$gDw7*B~&`*ESvs3<#%-#NA{PnHR z_Tb44PWAW?S)0{8@VkQo?vmtco`*S1@o z)$SAV$?8?}$1v^V`R%TC3VIyJyZ8VzHuUFVuZs)QohR-d9=9LRdV*ILj&;Tk?c8*} zkeEa8z~vpbA+Xs72g|2Z7nwNKWtT+e++>Z)yDpafk!!OUb+A*hma$-8}7nf9cs|;o(~QEM#;Vh`;{3tkG&J^UeOc z(7$?|yH-QGzW;k&v%dINWcJI^GC}hM#~$3%tQn9k*l!y)*XRM_0ojP%c_PUqmzR_m z@cxapWf>b;<&MvM7+~MPV%pTlqL)p=aY||@b#Lk~?Q2JJX(`k(uGK-a0n54h_5e$YAIm=NC~Y{WZF%tse!xE-$7pC7`<@;Q$-_;wl_5ACz0`VU6mi>2@DS^vo^oZNyv46UQ}b@F_KIU}?F zTQFfmp04?QE)9>VJjsiktW@WZH)~Ca)|fZ#4`es|8+(=atqiXx2=kV{hi6xpmzV#j zrFHIj-^Bp)xfe(ee?L90#CdB8zC*F}L7u%yjM9DC&sy>8?RPrE1C975{6ujKI*tuvV9Jy! zNyy?0)pnGGL&brWTrRHE{wi9R6IIJq0mftF!FM3` zrq=0Z<&AL$PWmS1IN#cKi%QQl^EtBPP>h$*_rAk7@Bq#`LfcyMpTz$Y{RtXPqQ9wS z>^_-2B(Cu50XDDv+%)I$yJR1XF(%}h#)>!mf;C0V5%|ZOhJP|L7BO$N-R$uN&RTph z*2`z?{2$?qk4;kFpUm33zGMGOES?zSeCYwmgVKR>kP#_i`5XD(%g2|eeHQQRFW>); z#va3K^2~aHm^=XemoiqEIYWC>1pNo$rv~`yu#SEJjK(?e-#^QbdqaDT^#Z%dta$CI7;N_x8y4?a0g;=b(w13_47_N?pyC(@Fh8S zUxyDVw2o;Xgbx&_RC`nS_bL>8=$CWwUFnDu;p2gQ_Sq)|oo}9{pP5$ycPY&4RZsYY zaek2QmIq|pFRHjO(ATyeor7)HpReSM=6{}I+uK?0`gM&SNGn(4(s|seP8WfJWx4oiIbF~^*Rx`FCHSjO@3E%Hi^8hQ9{4F z1{r)U_TJwpp=(^Hc_n!7cj$)KqkG@GRb;#G?qoz?GWGPg4z&b*$H z``Grgaz~qW`F>p+I)4E_^=nOp!UxH~_=F!uT_-1noU1SB7k50c6dt(t!hWnB!j2R* zPY914)S57Et$DF-H@N>B?N>hcni?%?lhXmTJ z_J8!|i7YQ1x-a`yug7ofk=ut7uQoUy)VJt@=eGr{TT7nMOZE5qMd^L1X@yRo(XWL2 z{UH4%18RuNncaDk;T8Y99)0`Q&}U3r)*b)85c|M|s-V(0P2-ind|CDk|4)`M&E^eyuF z>b8*kAyE=eF&}Hsv`|qH`F9x3rXruFKTYo0~i8AK#&O^R5 zm+N@R|DJUIdCEh1DNp6CGT_^9{IKg<@SpnQ(ztrSQhbJ{?^Xn?!LbT?zX2{hUj$&= z<`3q?vl#<@Kt3gvL2bvKN<5y z%YS?S^n)XvF04KE!z|tb7l-p~yE(A)o=#;wJV<_~q~v4Aebc3Xczkblf9Zje3+b%i zQTy34>5mSIt@^k_dd8){_N+Z{k0Qt2r!d09=q?M{hahQtV1TpEIx7hqOCUyv=$~IS zBVgOLv^V2E=6t0W_-(0v{@GHl)dv)IO*8vZ#+oOVzt{ri4|B7n2L$?ob?CzTZV&X= z{!8?E-o8#r*zS^vt6DyM3419A?R@S$Km9)Y?dF@^@)AYIY1+<;bme2_d9G3rF zm_p99sl{>T1>{3G-kE|wk>6&e@6jp$ygkq#KL3Nom;AGYhrF(PGS*+)#NIOmJwP$H z3wr(^vNhrdsPCcs8|Y${$db}{iFhz^|MP#dDenQp1zvr^eVXFRKKF3Em~;3DT-G1| zv`G8c_wde__#YK>z0Dr@@@Ha0dxr|TdY|k)^qE?l6U(PZ{r&*fZl|PlacwyyH{3N|B2`Uw2g~4dxhE% z0LD4fg(p9*((%oIP8K!dys1%n&=!ht=Q9l#WzaGk|ph7!)QYi$ITjBZwR_AvUSy2dVH5&|JP)^ zXLLSfzvg$fUfaK~xA3evgDuIA2Hm{^d9)%@H+TTO=9&xpI`2LkTt6cED=oZlgu6#& zSZ?TB`m~k!LG*NVFAuv((618umkyd6(~sNu^^APM=m+xu10g1XqqSRF{H?=V8PSTHpGi$>YS^LD@zhAbnz+JFuIMpzV5d=#uHl*!u=!&o?$7pPg57 zU;039!QLL-L5R7gzx`;!gbAl1cTXKTa^$J(73?MEXignFcC44!lf(OOW8U-UrR#Y4 zg`eMSVRX;3X(T$&&mSGr9-#FkXZ@ty8`Ictv188P7W)9&hp&%xZMI#C4mUc#OYh)Z zPyb1uvsT~gNG|Oq`)xmK@wFwK8ImTE5&=pCIDPo%Om zvx$sjJ>q!E*k+F5!-vb>|B0nP6*D@0)##Vw(!a3e6w~+X-K{nuue86&qWjr{AZi?F z#sNIre~)fW>W3fNtZjz)R0Kblp3uI*($UQGvwx!I^6gm9e^#8H^vue@xX;f!HlHo+ zEEofDn)?Lj_gD09()J|_-bDvEV`fjkIzN_yrM&lj_0Sl zy{;bjo*SzrJkKYbGIngl7jqGFM{||vQ%th=@y_l29&Pt^e_eGwW8dyg=x^)-wEsJ< zu5o>YsiXaly1tHIWSjUgQAWET=zf<{s8>J5$|&h~eGGqoR|o7p;yG++u5X|H4jb(| z4?g(d79VewzV2uXvpx4>pK96s{QbX`|NmMr+DWI6@Xu|b1Aa<8xlVk))gSowQ?<~e zRK%md+JD*Lhhw&XjRkx3y%Cwn`##+m+o8r+Tl|5nn-u5+s7I%)WanRT`~u!!|DoQ! z)b^#qRqV_jfKnBJi%>$9{Ae=PmvqZbf(e zgqHhxAcOshpTyqhZ~yjM>xX0q^1n|do!-5B58C1@+YA61%W7@Uo2rvPRk2P#+P_!* z|3G~I(z5#p-i3b}uph;Y+t3UU+Vz!{D_4e)Rd4#~naq6912+wcNq@};PiG7e)&4cV zbws-Y-bRJ(zWeT}=tS?4y^p3L=JY>}Y4`2F!#BilDDu5)_FPznK0KB=@?x9lBa5RG z?auq(zNcr+dC&h2I%6?&i*eWIun$Zc`FG>Jn06X`dgu7*uj?BwBerM^8-VD4#C|=S z&>wwvoA3*z{iiXn6|nm!CT8Hv`DGLN@A`RtfUiexfd1gGkui~cE+tpnJRxtEV~2l{ zm<>h#W^u|XrzFs}j{%nVfa5FDC-B!b#CZzv0kOZX<3Fzs@^8}q^s(tb!8z{W9>%VW}C1yDvJp){%M#^UlZn+ckd}K4855d+#MKu;0p;&*Krhb{PH-D7#uSdo>S6DD#f_l_%9z}H9cUfl7=dV$*l|3r_@M+bch21ctF>N`kP}C$9sBDeUsu&T4?gIUn`+|2MEim3 zePrFG#Jk4?x@{cz-e~Rg3}m7g-$(TfCgg+fnUxoewI^gey|gZ5s7 z3)Pvn|0Ht=f&OG`tba7j=mAOSfv3X*t7EnE0G{9j_be=yUb9eG3=%+MfCU4e)lruDkBK)yqKQz2#v4x)FVK zwZE?RI(%MB|9CgF@5z0;9{3uD)|VO^8$%y_@PT>f`|rOGty;Azk@x*6>AUZ~OZxot z&y|wD{PN4>Z@&2^h3`^%FO8Hz%4%q6$XT{*S^igFeO3JM!w-*O-^4$mKdr$&B$?-6 z-*@)ici*qr>toiPcivgZyXoYY^4VvfCGowIEz`jA$tRzL-hA`T^}^0K39XRUTgLnE zzaRa-ffGC>0lRRK`tipfXJSvD%6_GKZbWp!A$|U z%me0ZQYI;#l(uHgnpFNv`QnQ&lEF*z^5x5uRXZQrQ5~?5u0PSd%rG00Gj(+oz~IgnZx7nHoGrX zIjbY(#k^l0^}lyN+X5Jdui5ureZv{Vxv#VNhySm05jET5ug(R`yRVOZpJ4GXdxSYG z-QwLFGzc-_Bl^D9F~>Ulf8?H=DP@-XKRPY<_5OP(U+3MU%eeRM>o7|Ah`JA>R}5p0 zFgi?DdJugdW-n8;{`c;~2nbi-fSSv*Qd=Gs&aL;|Q^BOVgoIlAKNU)v9RVLs#`A!F z$ve1ddhdZBu7C$_xB?z{VSs)224D}|Fhxqe;#>;VM(zzoW<~Bbs<6~BPlMnYjQhYd z0Q$f)00!>sJ!WzX;ob*s)AwilsG~0^KTsa`KJ*Ep0eDS)YTcsrYdF$(Z}3y9f7~M) zU-in*2Vh;){cPR)DyUmOfaX2|py)l@2cY;P>OQT`$1l_~;oeVvDKszcU+`#^gY_S5S5ndM^FP=j0BAPhBP-Iw~P zq5n5@ALj6S7cvha^)BQd0_xoR*%l#nP2`~p;|L?D&fG-qXE*mgj6m;w@7@6F$^UT0 zT5t~lq18XP1aZNs9}~PkvW54{z4r~gNs;_#yM*%k)7eq|ZJm4X^~d%8{@MjD@~c3J zJCJ6RLJ4L3a_nG!TkYqz+ipw1cAmUB^m%lwBt2=M>V#y2PpG1(x;^Rfn`XX*IWHD_OArSG(TfZC+TnO8}IU|xT8Tb1k2m-sX_*3-i;QlaA^|+=nh69vJJ*VNKaI zCG2C<;8AmK&NJ4&neY7H+Gp5}r@?Qps9dd|>DptaQ`n2O6xdhwjQ^J6OVj4<0bV;% z<;m9n_}#33V4hnsuxgnH)AP1YvLzH({vzySiccvq2J}qjGPl~FbDJKty>lplxQ|KB z>rb(7OxXA5r~`YndEHg6{K+bZ)p zKDzXT6Hcgrm+n`&NlBepU$EM%zxIO@|4Sz0pVwx6k3}jnraRZB@B77BeZ2a=hAha; zXd1uL;;OaI+4)n#vQKdo8lBw2eGLCo{|~WCCt5h$>dd~H$@KGIs6M;zSm4!Pe6YjV ze2*7ftG@nx7`!`0|Lfg#lB;gdc5Cv%xi}C1q*uN zF`V1wpl&Bokg4kf?1y3N|MJ2yPHH&*5jeW%i6@@eWO(qGOa+f`sl3dbQC{8UQ{KJz zuMF>luLf{by~%|&GXv&uy8)BgxA-9Tg0S`1`G7rLA7@}%0}gsMGt0QIA#V@nPa|8k zH>zDrr@ivhvicetP|iy?-gsl&Jlf(9`r}6hSAJeOlB7+RqIN9`vyqwk^hbUqyFvgQ%xVgkIGg$@8n=CfIX%Zn&4tbmUh9Jp3SaxQBGN zFX{ds;J=4=?j_xaj&nb04)pvZc`xRFQ^$;~>Oh}gHR>h$dv{+}-fLgR_hA{CtF=o8 z#&e^1;3tQb@Z5zTd3l=evkhKOojNr{z3x@s8tZA#gh(8<1}CpzFQW&8pEc0F&jvGW zqZgzLaJ(rvJLC7FeNn6|d}ZNSrQUur)Yaa>|YaC zt@^RY9t+KxGbiD$yY5O@uwa4wM-BoH4Z>4a&Pb=G_FJ#r*Wf>#*=d-;A@9h(FtR1E zwa2^I`lwoRop7N2;cmUMhJ7OuLb?NOx`m%+Hd2lX)V@(QqxF46uuJ{%l>Xiu{qUh)9B`H*b**lfCi zk9YM<`+Qamek8#6<%1vPxf?GCz2NWDr%!Kl8)u6v?)v~;N3k#s9}gdxUUM~gcv{!$(~2uE4b;8gxOYt-;Nm86IdW#R_4U3guJ>}k{r1!RzzwRC z(lG}G*T=m0_!y@TGSbRJ_q~wQ*~ATr><=Pc-|PcMn-D(1(|+qMcnkVd>qz0%zqxb^ zIlqirBlws5!}2iPFJ|SAU~KbUpv`yb@nerq;Hj%6C%2{p-d2EsPkL2)PJHsI5@z|WGlX)-!}7Q8p}(sm#j~qj*6dm+f_9IdH6VU zNIBW=zN+*c-!di`ycJhnv4h7_kJX}k3h~hUjjwn5j9?vXcpR78DcO;|)ZZuE^_zk0 zkL=nt>H9uF4s7-@{>IRNF^&5FPgVD%*IQ{&*n2x!-fDYp=I2ob;$TKzeuYT??J1zL@&2VK-5n zZQ%f3SjU)RtLP8W)%TbDR-HPrpXET`oW;P&Yn_}fKcY@%Pa?|C(n3^|VF~FNof(QQ2THq~mEP#%a419H}dx$s)<%4CrjKsb!9r%FRM@;{t z3;i3v$sDfDfjw)m@Fu&`{yZZcK8{I>?Du;iyLs=LLF^1G4!l>9jO`HAI57U~^z>hm~n_Jj7gH$WJgb}eDg z6R*uTgvau(?UMRvkCdsLr@|{!kpVwKUi^sfcSBFy9b3U3*i809hRS}pOnG+APYw8( zy-7^Te!a78fAAge7jI4kAiq3lNsxfb%^GEi0 z;Jr`a`K8cQbH(Bp&mSb}`VGCKcWEDr+hTCoyDZBQ?^{_g|F)r?P5^Jow$kng?bY~y zNLI@SIWRv@{F9)4d&4rH3V8eyeGN{WOMSkE!oyVTNMRmWhWu!E8zKDpsNhUxJl}uP zS03&Aj{c7~wu6pOLC33=c;B4ueRH1a8}fKhmgRwa7$*xaE)1#iM{Bo3 zb5_8$k^-HsJ!g7#SHG9v{b&Odu>C;WEZtO~kcCFZsILBb1j&K2p5A=$OYJ#6M0Xdy zO+70|{MD;}P3?l7=6H%gR+;loBtnt`H_Fm`aJkvYy!2gu5d`TA1iNqV$p0jpbfDD+) zJ8ga_1`_lzvbl57K_2bZ=jF2BCjhiz3OT2+&cM=N`!M$E;l^1C)1=MokKaX4TgRT& zH+%J$&YzU5c}=s|&sX$$uhUMvr27p$v+wxdv)ADKd*wZebvCyCiZ!;&B=rO0jl>U` zPj7SoZ{YFtUH?8WRoJwwJZ9eJNMLH!gEnCFp{&d#N3qo{4$e5PjQSh<$ZFi7(p$CA z7@1LjW6$QVfNxgM{g1(z|VW(5BBNf3t_#Kwq50t|cdXaOnSyNyxB|1v`A)8{Dl^9z1(i z&+s`Pp?lR|V<5}_Mh^;?$9Qmez1x-t^!PvWr+y(vx}KrGv1?8>bp~$5bp8R_{lN7a zUs8N1n^tkHv{H%uB-^xx(~Re^jb%{pCFG&;%|)sYwC%>b>s0?Uj*r+$&p(B^f=K;q z^ln=o%*QJRF1vX2rXRlkth3I_CH7!0u?H2GFq?B0_nD*&?$b$% zOPI=i3MrXu#T^WQO)SC$Z0X*-2mP-6EMHK$@~_{2k0P`GOse)6;TiueWvytN`$j3| z8}0jaXko_ZD%&PtcpcxDT<+WCK=zCV_p#Rhe#;npqillGoBfG+JfRDWGw#^(Du0?c-AR8N209*&rE&DOZfz0 zXsjiK4^LMUEMYm<%gLs`)C5~GksX$cl=?6F3@YkkJ=c5od(NvVPWSdKsir^&f%7bd z8LC~a00)f;rRFTI5~Ml{YDJSV=elsfh`P{h6F8zi9B={v0mlGZsx#BB=1f7gx^J|B zG2Cp)*knh>;j_ID|8L*7-`o5s$K-3vv*lZOEPS)W@Fqmgs*kCkIfqPu2ZM)MQTV8h z;LGAov&eqDdi1b#u;+w?^c})?R31T9UbL$+6oyoYVls;iNx4`-mui^t&^VZN`?*c@_5;6X$f3D*!Ii=@+$s zgeWdu`G4pVdo5nPc$4$C8PGTHOuh}-naR$+Q@b&jlp;FI&-4sp3QR&yOd=k^Bw`6n z(mk@=-0ND;^q-#j-_RJiT;+T->`NfAD_{pa>3XF}urHxHiIeYFDne*6-`42s7jimK6 zcD$AjEy!~%@b}={DhK*mf#@t-t#Gd~%CCuqP3H3BeIGu$od18p{iV%GdKVg9#kZ@a z_iMeg;O{@-6KJX!(@8Hw&sMW}FsCXV`>%rcp_|=(ofPAvQ?`+H-_h0z`L9I>{5_uW zNrQY_E$(~vyUE~$vee#M^`N{H$k6exMLSmOE_eq1Zv&@CwIuP)Q^I{o|J%awufs2Y ztn$;E>j|w2Jekqnx&q${?XCBZN8I=t&pb4ez4qL`O!a7K()v+5Colgy^Ugd%od-PU@%(&v;sWIGEb_RFXN;EWoj}NePY&SJpuNyvVBL@E@XDgG;q{QlPW~D^e;RfCsS-SM7WY~c$sQ61db*D6 zOyP9_-(!Cnq`ceXV63hF;9d>j4&1ZY>(jL7quK^AuR{h-7cAJUWEU|!pOHPl84#Wm z9mu?RR_++vX3uNywR^0|%M~AK?&ykhB72qHjGvvJ@t<_~fz(55uGeXgHtpSEFu=j;_AIVt_$Nli&~Zuw=zx9Rp@*LZ6Ju_5h;m27i? z8Mj6sL)IJ{7IvX0#dpVEm#r8SI0FZh$^tR52R_ z6I-S@-FfcMkyv)e9>Ttb&<8$y89H>@44^hClKIaZ!K1Z9-OA5J#*N+lA6{enDCr29If`rGw%I+V z6nOi|{~Oy&AN=0a*vkh$3{0tNpI>2fslMo< zixSkw^8P;H`iIWBDb7M{kCI8U!|cDi8OAF!-U));Wydj|Jv zx1Bmqw(V7MF0|IgB;Ng22_9O(d6nvLIAb;2FWh;JJNBf`@1uvb*&M0rf8KfLB_Q|a z2?oE-Pd>`ProQQ~@IofMlMF1$%q^#AT@8Ddq!Md0jj_4zd!;uzp;)LcW&4l4}q#WzXGIf8%HhQ@%*WoYavKl(=b4qHB|dtfAc(j+Ml z?E~GVE#PNQJIx`P^$;cf?{N8szN&N4p@wfLy8#}M{`LXozfY=#tAXo#(Z_SH6p{FCOpgM(k*nMVmWN@3+Q5TdD>Q z^sq#H^I0>C?T`Dl=nLaEHGkkdmORWF%%Xxc13Pv4FTNFd&BdjfIWG^J*M`pQ?Wb=-*IjpABK_XU^tsEy zlSBLXJ2vYY{!7MpED;z&7AE0yiRUi+@m4Q3ZG>zilo`#O!sv-M#VbY|HI$- z@|mUgZT_}A;3(B|3lH!b`07Km4ZPIHo&)uoy+J(X#m1M5=Y;Ulk8>Gm2a@NfmB&pp z4`T64rf84zGvH-q*P?@VJ(6#Kh71_hq7TC_^WBeS@2lx+)~~LB$0wt!RHLI*qraFl zI!m?x8U9GsddB>OVk)dt{BPQ}VnMXH&p{SFaUApDS~JqGcVulM;6c?5r(E{0SD%_! zI9;~YKeQAFl)vEk8I;d>wsuTs^~pD&FO?#jO8sa5wfzq{^GMf{Q{3{q9jx-@XVjvD zJZ_RNiOLrq$m1>Kz%|I{tHGz%*#(lG@&DDpa}73;&$^p6j*TV;JMW&;qQZHZ@{TVq zOf%)*dgTCvbMkqTzqFRhCQTG5+53mU)AQE%qo#I+iJb#%Q+TIE2lXh2hZ^SHGQ`-} zx^+zx4NlomCcyaXOyU-a_9ljdyVuZ0t-Ytbrw`i8osT}-UCWMiabv*7JFEXHt(jxW z@07o@#_qRm2BLDq(Hl6mmKk&XKUk}Ba?>*vg zYI}|JOM~O(zoF;Eiw=?h^Rgp|5B~rksD3;AC(1rUHZFIsmj)+0Z=zuRL)nJ%Z_fYJ?MnGb z$-ho?7*N$oI_@Ik93|t=l+FJyD^84j4*Id*gZvQq|K#T6-JExr6HWu?FUv-*y_=q5 zO`&Xt@W(aac&@ecYE5@$&Iq-W10oX+=b7i)-|ZE(wo)Zx^(}s z`27QTuAn*fYUw-h!oEpKi4E4@@zS3$cLUE=40(bz-MZYX#N>8vrX`B;UxbY z==)|)*FBu9ym8LJer_Br%_%G13oz87v!>ejTl0+mLD7lxWvi&EUG7wmt2eaYX`J+7_lb?P{X<%_ zeA+qo)x@=B10OW)OnWH71K*+x42B zkJgdzUBGoWW#5AgyqEJ!1FEvkT2u9x@;w1JM{i2`@Ov?3eBW!pU8e4e6+k&}Am5tA za_3PY?|x?G<7uw1%SX^?qiv?}(UszN&E54jHeEA5)*3yn=a%1GA8!l}=-1fCC-k{pGo8yG7UcC9xZZVriFaN+VtRMeN6chT4c%`{dvy6a-BDZ` z&F9EQfb6@9vBeB{a)!?SWS@cWJLHS1cy!`NI>AQ1Yw-#h`0nB?bQtw(J*uu#dt1x1 zFPf8hcb$!kCLNk@WQ%Cg0p8|njjGD;jPE-5xTColz&C$C@d(#;x#ReJ(cp^aerw+Y z=0)Ai-d&Bz`tiy`^B+rMCHP?uYhW{YXFg?*2iDuI-n{SBLg_+x+I*XPru;ipZrPxR zoTQX|*)8uQpGzp~BL2IOG@J6|mwO)1evEBI{!XVt|8o92yBG71ibYpmk|90xqIl(s zH^2ylcOSb;dT6K-?MS+nOI=pU0)(|bI*OKrLH z@U27XZ_?($@68@}BqeA3s$%Gq*l4D$X5X2$((6-_ljMW2!2=xaQe1gSqx61ocRT69 ztIqE$Tgf_jVBGpL0Pa+LeBY@W^&+}^_WjUr8u{(Vo=EeB&%Tw}FC#}1`cU8+jEwxrZC4F&hV;+*hrfKuD%MF$&V{mbdfk(gKlW3~-2+@r y=DS+b7``pxS^rT(^OqHL&HAn0Wlge?RrP61d`G3_xd{nMt4kwE_tEbOj{gUAk+-q{ literal 0 HcmV?d00001 diff --git a/packages/playwright-tests/barebones-template/assets/header.svg b/packages/playwright-tests/barebones-template/assets/header.svg new file mode 100644 index 0000000000..59c96f2f2e --- /dev/null +++ b/packages/playwright-tests/barebones-template/assets/header.svg @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/packages/playwright-tests/barebones-template/assets/main.css b/packages/playwright-tests/barebones-template/assets/main.css new file mode 100644 index 0000000000..90c0fc1c09 --- /dev/null +++ b/packages/playwright-tests/barebones-template/assets/main.css @@ -0,0 +1,46 @@ +/* App-wide styling */ +body { + background-color: #0f1116; + color: #ffffff; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + margin: 20px; +} + +#hero { + margin: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +#links { + width: 400px; + text-align: left; + font-size: x-large; + color: white; + display: flex; + flex-direction: column; +} + +#links a { + color: white; + text-decoration: none; + margin-top: 20px; + margin: 10px 0px; + border: white 1px solid; + border-radius: 5px; + padding: 10px; +} + +#links a:hover { + background-color: #1f1f1f; + cursor: pointer; +} + +#header { + max-width: 1200px; +} + + + diff --git a/packages/playwright-tests/barebones-template/src/main.rs b/packages/playwright-tests/barebones-template/src/main.rs new file mode 100644 index 0000000000..4593314917 --- /dev/null +++ b/packages/playwright-tests/barebones-template/src/main.rs @@ -0,0 +1,37 @@ +use dioxus::prelude::*; + +const FAVICON: Asset = asset!("/assets/favicon.ico"); +const MAIN_CSS: Asset = asset!("/assets/main.css"); +const HEADER_SVG: Asset = asset!("/assets/header.svg"); + +fn main() { + dioxus::launch(App); +} + +#[component] +fn App() -> Element { + rsx! { + document::Link { rel: "icon", href: FAVICON } + document::Link { rel: "stylesheet", href: MAIN_CSS } + Hero {} + + } +} + +#[component] +pub fn Hero() -> Element { + rsx! { + div { + id: "hero", + img { src: HEADER_SVG, id: "header" } + div { id: "links", + a { href: "https://dioxuslabs.com/learn/0.6/", "📚 Learn Dioxus" } + a { href: "https://dioxuslabs.com/awesome", "🚀 Awesome Dioxus" } + a { href: "https://github.com/dioxus-community/", "📡 Community Libraries" } + a { href: "https://github.com/DioxusLabs/sdk", "⚙️ Dioxus Development Kit" } + a { href: "https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus", "💫 VSCode Extension" } + a { href: "https://discord.gg/XgGxMSkvUM", "👋 Community Discord" } + } + } + } +} diff --git a/packages/playwright-tests/playwright.config.js b/packages/playwright-tests/playwright.config.js index d32c86a732..321935dd0e 100644 --- a/packages/playwright-tests/playwright.config.js +++ b/packages/playwright-tests/playwright.config.js @@ -176,5 +176,14 @@ module.exports = defineConfig({ reuseExistingServer: !process.env.CI, stdout: "pipe", }, + { + cwd: path.join(process.cwd(), "barebones-template"), + command: + 'cargo run --package dioxus-cli --release -- serve --force-sequential --addr "127.0.0.1" --port 8123', + port: 8123, + timeout: 50 * 60 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + }, ], }); From 6e05bf84c1b9c8d95a2a7c0e17d61a55615fa340 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 6 May 2025 22:17:34 -0700 Subject: [PATCH 298/301] fix compatibility between the two hotreload engines --- packages/cli/src/serve/runner.rs | 32 ++++++++++++++++++------- packages/cli/src/workspace.rs | 4 ++++ packages/web/src/devtools.rs | 6 ++++- packages/web/src/lib.rs | 40 ++++++++++++++++---------------- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 96841624f9..8a68979f3d 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -311,6 +311,12 @@ impl AppServer { // Get the cached file if it exists - ignoring if it doesn't exist let Some(cached_file) = self.file_map.get_mut(path) else { tracing::debug!("No entry for file in filemap: {:?}", path); + tracing::debug!("Filemap: {:#?}", self.file_map.keys()); + continue; + }; + + let Ok(local_path) = path.strip_prefix(self.workspace.workspace_root()) else { + tracing::debug!("Skipping file outside workspace dir: {:?}", path); continue; }; @@ -341,7 +347,7 @@ impl AppServer { }; // Format the template location, normalizing the path - let file_name: String = path + let file_name: String = local_path .components() .map(|c| c.as_os_str().to_string_lossy()) .collect::>() @@ -392,15 +398,23 @@ impl AppServer { // we're comfortable with both co-existing. Keeping both would lead to two potential sources // of errors, and we want to avoid that for now. if needs_full_rebuild || self.use_hotpatch_engine { - self.client.patch_rebuild(files.to_vec()); - - if let Some(server) = self.server.as_mut() { - server.patch_rebuild(files.to_vec()); + if self.use_hotpatch_engine { + self.client.patch_rebuild(files.to_vec()); + if let Some(server) = self.server.as_mut() { + server.patch_rebuild(files.to_vec()); + } + self.clear_hot_reload_changes(); + self.clear_cached_rsx(); + server.send_patch_start().await; + } else { + self.client.start_rebuild(BuildMode::Base); + if let Some(server) = self.server.as_mut() { + server.start_rebuild(BuildMode::Base); + } + self.clear_hot_reload_changes(); + self.clear_cached_rsx(); + server.send_reload_start().await; } - - self.clear_hot_reload_changes(); - self.clear_cached_rsx(); - server.send_patch_start().await; } else { let msg = HotReloadMsg { templates, diff --git a/packages/cli/src/workspace.rs b/packages/cli/src/workspace.rs index ef5bfa84be..16e858d81f 100644 --- a/packages/cli/src/workspace.rs +++ b/packages/cli/src/workspace.rs @@ -321,6 +321,10 @@ impl Workspace { ] } + pub(crate) fn workspace_root(&self) -> PathBuf { + self.krates.workspace_root().as_std_path().to_path_buf() + } + /// Returns the root of the crate that the command is run from, without calling `cargo metadata` /// /// If the command is run from the workspace root, this will return the top-level Cargo.toml diff --git a/packages/web/src/devtools.rs b/packages/web/src/devtools.rs index d6fa7c77b2..caf20e1388 100644 --- a/packages/web/src/devtools.rs +++ b/packages/web/src/devtools.rs @@ -115,7 +115,11 @@ fn make_ws(tx: UnboundedSender, poll_interval: i32, reload: bool) &format!("Error parsing devserver message: {}", e).into(), ), - _ => {} + e => { + web_sys::console::error_1( + &format!("Error parsing devserver message: {:?}", e).into(), + ); + } } }) .into_js_value() diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index bc24190385..6d7a9de9ec 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -64,27 +64,27 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! { let mut hydration_receiver: Option> = None; - // // If we are hydrating, then the hotreload message might actually have a patch for us to apply. - // // Let's wait for a moment to see if we get a hotreload message before we start hydrating. - // // That way, the hydration will use the same functions that the server used to serialize the data. - // #[cfg(all(feature = "devtools", debug_assertions))] - // loop { - // let mut timeout = gloo_timers::future::TimeoutFuture::new(100).fuse(); - // futures_util::select! { - // msg = hotreload_rx.next() => { - // if let Some(msg) = msg { - // if msg.for_build_id == Some(dioxus_cli_config::build_id()) { - // dioxus_devtools::apply_changes(&virtual_dom, &msg); - // } - // } - // } - // _ = &mut timeout => { - // break; - // } - // } - // } - if should_hydrate { + // If we are hydrating, then the hotreload message might actually have a patch for us to apply. + // Let's wait for a moment to see if we get a hotreload message before we start hydrating. + // That way, the hydration will use the same functions that the server used to serialize the data. + #[cfg(all(feature = "devtools", debug_assertions))] + loop { + let mut timeout = gloo_timers::future::TimeoutFuture::new(100).fuse(); + futures_util::select! { + msg = hotreload_rx.next() => { + if let Some(msg) = msg { + if msg.for_build_id == Some(dioxus_cli_config::build_id()) { + dioxus_devtools::apply_changes(&virtual_dom, &msg); + } + } + } + _ = &mut timeout => { + break; + } + } + } + #[cfg(feature = "hydrate")] { use dioxus_fullstack_protocol::HydrationContext; From 815bdc6c627fcb6682693e5ac181860445013783 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 6 May 2025 22:19:35 -0700 Subject: [PATCH 299/301] fix preldue of server --- packages/server/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index 063a0bf98b..e2e2649443 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -92,6 +92,7 @@ pub mod prelude { }; pub use crate::render::{FullstackHTMLTemplate, SSRState}; pub use crate::server::*; + pub use dioxus_isrg::{IncrementalRenderer, IncrementalRendererConfig}; } pub use dioxus_isrg::{IncrementalRenderer, IncrementalRendererConfig}; From b637671ffa960d235a3f0b6212263315600c0abf Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 6 May 2025 22:31:25 -0700 Subject: [PATCH 300/301] fix check for wasm-opt --- packages/cli/src/build/request.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 66658c3cee..cce3820049 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -2942,7 +2942,8 @@ session_cache_dir: {}"#, // // We leave demangling to false since it's faster and these tools seem to prefer the raw symbols. // todo(jon): investigate if the chrome extension needs them demangled or demangles them automatically. - let will_wasm_opt = (self.release || self.wasm_split) && self.workspace.wasm_opt.is_some(); + let will_wasm_opt = (self.release || self.wasm_split) + && (self.workspace.wasm_opt.is_some() || cfg!(feature = "optimizations")); let keep_debug = self.config.web.wasm_opt.debug || self.debug_symbols || self.wasm_split From 6d2c10fb8373b8456fa3f7db8447bd48ea895d5a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 6 May 2025 22:44:35 -0700 Subject: [PATCH 301/301] small fixes --- packages/cli/src/build/request.rs | 6 +++++- packages/server/src/document.rs | 2 +- packages/server/src/launch.rs | 8 +++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index cce3820049..359156ce43 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -985,7 +985,11 @@ session_cache_dir: {}"#, | Platform::Liveview | Platform::Server => { // We wipe away the dir completely, which is not great behavior :/ - _ = std::fs::remove_dir_all(self.exe_dir()); + // 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())?; } diff --git a/packages/server/src/document.rs b/packages/server/src/document.rs index 28717d0aba..be688e2f98 100644 --- a/packages/server/src/document.rs +++ b/packages/server/src/document.rs @@ -13,7 +13,7 @@ static RENDERER: Lazy> = Lazy::new(|| RwLock::new(Renderer::new /// Reset the static renderer to a fresh state, clearing its cache. pub(crate) fn reset_renderer() { - // RENDERER.write().clear(); + RENDERER.write().clear(); } #[derive(Default)] diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs index 54ba134da1..1c2d21a30a 100644 --- a/packages/server/src/launch.rs +++ b/packages/server/src/launch.rs @@ -103,7 +103,7 @@ async fn serve_server( let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - tracing::debug!("Listening on {address}"); + tracing::trace!("Listening on {address}"); enum Msg { TcpStream(std::io::Result<(TcpStream, SocketAddr)>), @@ -202,10 +202,7 @@ async fn serve_server( _ => {} } } - Msg::TcpStream(Err(_)) => {} - Msg::TcpStream(Ok((tcp_stream, remote_addr))) => { - tracing::trace!("Accepted connection from {remote_addr}"); - + Msg::TcpStream(Ok((tcp_stream, _remote_addr))) => { let this_hr_index = hr_idx; let mut make_service = make_service.clone(); let mut shutdown_rx = shutdown_rx.clone(); @@ -248,6 +245,7 @@ async fn serve_server( } }); } + Msg::TcpStream(Err(_)) => {} } } }