diff --git a/.github/workflows/release-fixed-dx.yml b/.github/workflows/release-fixed-dx.yml new file mode 100644 index 0000000000..821f4faf49 --- /dev/null +++ b/.github/workflows/release-fixed-dx.yml @@ -0,0 +1,182 @@ +# Custom Release Workflow for Fixed DX Binary +# +# This workflow builds and releases a custom dx binary with bug fixes +# for use by the team and CI systems. Based on the official Dioxus publish workflow +# but modified for custom releases with the 'optimizations' feature. + +name: Release Fixed DX Binary + +on: + push: + tags: + - "v*-fix.*" # Matches tags like v0.6.3-fix.1, v0.6.3-fix.2, etc. + workflow_dispatch: + inputs: + tag: + required: true + description: "Release tag (e.g., v0.6.3-fix.1)" + type: string + +env: + RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }} + +jobs: + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + release_created: ${{ steps.create_release.outputs.release_created }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create release + id: create_release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.RELEASE_TAG }} + name: "Fixed DX Binary ${{ env.RELEASE_TAG }}" + body: | + ## Fixed DX Binary Release ${{ env.RELEASE_TAG }} + + This is a custom build of the Dioxus CLI (dx) with bug fixes for team use. + + ### Features + - ✅ Built with `optimizations` feature (includes wasm-opt) + - ✅ Cross-platform binaries (Windows, macOS, Linux) + - ✅ Bug fixes and improvements + - ✅ Compatible with Dioxus v0.6.3 + + Binaries are being built and will be available shortly... + draft: false + prerelease: ${{ contains(env.RELEASE_TAG, '-fix.') }} + + release-cli: + name: Build and Release DX Binary + needs: create-release + permissions: + contents: write + runs-on: ${{ matrix.platform.os }} + strategy: + matrix: + platform: + # Windows builds + - target: x86_64-pc-windows-msvc + os: windows-latest + - target: aarch64-pc-windows-msvc + os: windows-latest + + # macOS builds + - target: x86_64-apple-darwin + os: macos-13 + - target: aarch64-apple-darwin + os: macos-latest + + # Linux builds + - target: x86_64-unknown-linux-gnu + os: ubuntu-22.04 + + # Uncomment for ARM Linux if needed + # - target: aarch64-unknown-linux-gnu + # os: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install OpenSSL on macOS + if: matrix.platform.os == 'macos-latest' + run: brew install openssl + + - name: Install NASM for Windows TLS + if: ${{ matrix.platform.target == 'x86_64-pc-windows-msvc' }} + uses: ilammy/setup-nasm@v1 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.81.0" # Use the MSRV for v0.6.3 + targets: ${{ matrix.platform.target }} + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + key: ${{ matrix.platform.target }} + + - name: Free disk space (Ubuntu) + if: matrix.platform.os == 'ubuntu-22.04' + uses: jlumbroso/free-disk-space@v1.3.1 + with: + large-packages: false + docker-images: false + swap-storage: false + + - name: Build and upload CLI binaries + uses: taiki-e/upload-rust-binary-action@v1 + with: + bin: dx + token: ${{ secrets.GITHUB_TOKEN }} + target: ${{ matrix.platform.target }} + archive: dx-$target-${{ env.RELEASE_TAG }} + checksum: sha256 + manifest_path: packages/cli/Cargo.toml + features: optimizations # Use optimizations feature (includes wasm-opt) + + create-release-notes: + name: Create Release Notes + needs: release-cli + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create or update release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.RELEASE_TAG }} + name: "Fixed DX Binary ${{ env.RELEASE_TAG }}" + body: | + ## Fixed DX Binary Release ${{ env.RELEASE_TAG }} + + This is a custom build of the Dioxus CLI (dx) with bug fixes for team use. + + ### Installation + + #### Quick Install (Linux/macOS) + ```bash + # Linux x64 + curl -L https://github.com/akesson/dioxus/releases/download/${{ env.RELEASE_TAG }}/dx-x86_64-unknown-linux-gnu-${{ env.RELEASE_TAG }}.tar.gz | tar -xz && sudo mv dx /usr/local/bin/ + + # macOS Intel + curl -L https://github.com/akesson/dioxus/releases/download/${{ env.RELEASE_TAG }}/dx-x86_64-apple-darwin-${{ env.RELEASE_TAG }}.tar.gz | tar -xz && sudo mv dx /usr/local/bin/ + + # macOS Apple Silicon + curl -L https://github.com/akesson/dioxus/releases/download/${{ env.RELEASE_TAG }}/dx-aarch64-apple-darwin-${{ env.RELEASE_TAG }}.tar.gz | tar -xz && sudo mv dx /usr/local/bin/ + ``` + + #### CI Installation + ```yaml + - name: Install fixed dx + run: | + curl -L https://github.com/akesson/dioxus/releases/download/${{ env.RELEASE_TAG }}/dx-x86_64-unknown-linux-gnu-${{ env.RELEASE_TAG }}.tar.gz | tar -xz + chmod +x dx + sudo mv dx /usr/local/bin/ + ``` + + ### Features + - ✅ Built with `optimizations` feature (includes wasm-opt) + - ✅ Cross-platform binaries (Windows, macOS, Linux) + - ✅ Bug fixes and improvements + - ✅ Compatible with Dioxus v0.6.3 + + ### Verification + ```bash + dx --version # Should show: dioxus 0.6.3 (custom build) + ``` + draft: false + prerelease: ${{ contains(env.RELEASE_TAG, '-fix.') }} diff --git a/Cargo.lock b/Cargo.lock index 1e13a591cc..24858f9cbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3106,6 +3106,28 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "custom_debug" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da7d1ad9567b3e11e877f1d7a0fa0360f04162f94965fc4448fbed41a65298e" +dependencies = [ + "custom_debug_derive", +] + +[[package]] +name = "custom_debug_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a707ceda8652f6c7624f2be725652e9524c815bf3b9d55a0b2320be2303f9c11" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure 0.13.1", +] + [[package]] name = "cvt" version = "0.1.2" @@ -3502,10 +3524,12 @@ dependencies = [ "env_logger 0.11.5", "escargot", "flate2", + "fs-err", "futures-channel", "futures-util", "handlebars", "headers", + "hex", "html_parser", "hyper 1.5.1", "hyper-rustls 0.27.3", @@ -3554,6 +3578,7 @@ dependencies = [ "uuid", "walkdir", "wasm-opt", + "wasmbin", "which 7.0.1", ] @@ -7404,6 +7429,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "lebe" version = "0.5.2" @@ -14047,6 +14078,32 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmbin" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311322c49474a7490feac58b26e6b6b41fb0d4c6d4f3dae0c9736a9dcd77007a" +dependencies = [ + "custom_debug", + "leb128", + "once_cell", + "thiserror 1.0.69", + "wasmbin-derive", +] + +[[package]] +name = "wasmbin-derive" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3961bf864c790b5a06939f8f36d2a1a6be5bf0f926ddc25fb159b1766f2874db" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure 0.13.1", + "thiserror 1.0.69", +] + [[package]] name = "wasmparser" version = "0.218.0" diff --git a/Cargo.toml b/Cargo.toml index 94122669a4..624fb8a2df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,6 +225,7 @@ cargo_metadata = "0.18.1" parking_lot = "0.12.1" tracing-wasm = "0.2.1" console_error_panic_hook = "0.1.7" +fs-err = "3.1.1" # desktop wry = { version = "0.45.0", default-features = false } diff --git a/README-FIXED-DX.md b/README-FIXED-DX.md new file mode 100644 index 0000000000..9ff7877ab2 --- /dev/null +++ b/README-FIXED-DX.md @@ -0,0 +1,197 @@ +# Fixed DX Binary Distribution + +This repository provides a custom build of the Dioxus CLI (`dx`) with bug fixes for team use. The binary is automatically built and distributed through GitHub Releases with cross-platform support. + +## 🚀 Quick Installation + +### One-line install (Recommended) +```bash +curl -sSL https://raw.githubusercontent.com/akesson/dioxus/fix/custom-dx-build/install-dx.sh | bash +``` + +### Manual installation +```bash +# Linux x64 +curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-x86_64-unknown-linux-gnu-v0.6.3-fix.1.tar.gz | tar -xz +chmod +x dx && sudo mv dx /usr/local/bin/ + +# macOS Intel +curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-x86_64-apple-darwin-v0.6.3-fix.1.tar.gz | tar -xz +chmod +x dx && sudo mv dx /usr/local/bin/ + +# macOS Apple Silicon +curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-aarch64-apple-darwin-v0.6.3-fix.1.tar.gz | tar -xz +chmod +x dx && sudo mv dx /usr/local/bin/ +``` + +## 🔧 CI/CD Integration + +### GitHub Actions +```yaml +name: Build with Fixed DX +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install fixed dx binary + run: | + curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-x86_64-unknown-linux-gnu-v0.6.3-fix.1.tar.gz | tar -xz + chmod +x dx + sudo mv dx /usr/local/bin/ + dx --version + + - name: Build application + run: | + dx build --platform web --release +``` + +### Docker +```dockerfile +FROM rust:1.81 + +# Install fixed dx binary +RUN curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-x86_64-unknown-linux-gnu-v0.6.3-fix.1.tar.gz | tar -xz && \ + mv dx /usr/local/bin/ && \ + chmod +x /usr/local/bin/dx + +WORKDIR /app +COPY . . +RUN dx build --platform web --release +``` + +### GitLab CI +```yaml +stages: + - build + +build: + stage: build + image: rust:1.81 + before_script: + - curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-x86_64-unknown-linux-gnu-v0.6.3-fix.1.tar.gz | tar -xz + - mv dx /usr/local/bin/ + - chmod +x /usr/local/bin/dx + - dx --version + script: + - dx build --platform web --release + artifacts: + paths: + - dist/ +``` + +## 📦 Available Platforms + +- **Linux**: `x86_64-unknown-linux-gnu` +- **macOS**: `x86_64-apple-darwin`, `aarch64-apple-darwin` +- **Windows**: `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc` + +## 🔍 Features + +- ✅ **Optimizations enabled**: Built with `optimizations` feature (includes wasm-opt) +- ✅ **Bug fixes**: Contains team-specific bug fixes not yet in upstream +- ✅ **Cross-platform**: Automated builds for all major platforms +- ✅ **CI-friendly**: Easy installation via curl/wget +- ✅ **Checksums**: All releases include SHA256 checksums for verification + +## 🏷️ Version Management + +### Release Naming Convention +- `v0.6.3-fix.1` - First fix release based on v0.6.3 +- `v0.6.3-fix.2` - Second fix release based on v0.6.3 +- `v0.6.4-fix.1` - First fix release based on v0.6.4 + +### Checking Versions +```bash +# Check installed version +dx --version + +# List available releases +curl -s https://api.github.com/repos/akesson/dioxus/releases | jq -r '.[].tag_name' +``` + +## 🛠️ Development + +### Building Locally +```bash +# Clone the repository +git clone https://github.com/akesson/dioxus.git +cd dioxus +git checkout fix/custom-dx-build + +# Build with optimizations +cargo build --package dioxus-cli --release --features optimizations + +# The binary will be in target/release/dx +``` + +### Creating a New Release + +1. **Make your changes** on the `fix/custom-dx-build` branch +2. **Commit and push** your changes +3. **Create a new tag**: + ```bash + git tag v0.6.3-fix.2 + git push origin v0.6.3-fix.2 + ``` +4. **Monitor the build** in [GitHub Actions](https://github.com/akesson/dioxus/actions) +5. **Verify the release** is created with all platform binaries + +### Workflow Triggers +- **Automatic**: Pushing tags matching `v*-fix.*` pattern +- **Manual**: Using GitHub Actions workflow dispatch + +## 🐛 Bug Fixes Included + +This custom build includes the following fixes: +- [List your specific bug fixes here] +- [Include issue references if applicable] +- [Mention any performance improvements] + +## 📚 Documentation + +- [dx.md](./dx.md) - Comprehensive dx CLI build system documentation +- [WARP.md](./WARP.md) - Development guidance for this repository +- [GitHub Releases](https://github.com/akesson/dioxus/releases) - All binary releases + +## 🆘 Troubleshooting + +### Common Issues + +**"dx not found" after installation** +```bash +# Check if ~/.local/bin is in your PATH +echo $PATH | grep -o ~/.local/bin + +# If not, add it to your shell profile +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +**Permission denied when running dx** +```bash +chmod +x ~/.local/bin/dx +# or wherever you installed dx +``` + +**Checksum verification** +```bash +# Download checksum file +curl -L https://github.com/akesson/dioxus/releases/download/v0.6.3-fix.1/dx-x86_64-unknown-linux-gnu-v0.6.3-fix.1.tar.gz.sha256 + +# Verify +sha256sum -c dx-x86_64-unknown-linux-gnu-v0.6.3-fix.1.tar.gz.sha256 +``` + +### Support + +- **Issues**: [GitHub Issues](https://github.com/akesson/dioxus/issues) +- **Team Chat**: [Your team communication channel] +- **Upstream**: [Dioxus Discord](https://discord.gg/XgGxMSkvUM) for general Dioxus questions + +--- + +**Note**: This is a fork of [DioxusLabs/dioxus](https://github.com/DioxusLabs/dioxus) maintained for internal team use. For general Dioxus issues, please refer to the upstream repository. diff --git a/WARP.md b/WARP.md new file mode 100644 index 0000000000..9c4f88d29e --- /dev/null +++ b/WARP.md @@ -0,0 +1,222 @@ +# WARP.md + +This file provides guidance to WARP (warp.dev) when working with code in this repository. + +## Common Development Commands + +### Setup and Installation +```bash +# Install Rust toolchain and targets +rustup toolchain install stable +rustup target add wasm32-unknown-unknown + +# Install Dioxus CLI (latest development version) +cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli --locked + +# Install Node.js dependencies for Playwright tests +cd packages/playwright-tests +npm ci +npx playwright install --with-deps +``` + +### Building and Running +```bash +# Run examples (50+ available) +cargo run --example hello_world + +# Run with hot-reloading using dx CLI +dx serve + +# Run specific example with web platform +dx serve --example calculator --platform web -- --no-default-features + +# Build for release +dx bundle --release + +# Build workspace +cargo build --workspace --all-features +``` + +### Code Quality +```bash +# Format code +cargo fmt --all + +# Format RSX syntax +dx fmt + +# Check formatting (CI) +cargo fmt --all -- --check + +# Lint code +cargo clippy --workspace --all-targets --all-features -- -D warnings + +# Type check +cargo check --workspace --all-features +``` + +### Testing +```bash +# Run all tests (excludes desktop and mobile) +cargo test --workspace --exclude dioxus-desktop --exclude dioxus-mobile + +# Run tests for specific package +cargo test -p dioxus-core + +# Run Playwright integration tests +cd packages/playwright-tests && npm test + +# Run tests with release optimizations +cargo test --workspace --exclude dioxus-desktop --exclude dioxus-mobile --profile release-unoptimized +``` + +### Documentation +```bash +# Generate documentation +cargo doc --workspace --no-deps --all-features --document-private-items +``` + +## High-level Architecture + +Dioxus is a React-like UI framework for Rust that enables building cross-platform applications with a single codebase. The architecture is built around these core concepts: + +### Core Components +- **Virtual DOM (`packages/core/`)**: Renderer-agnostic virtual DOM implementation that powers all platforms +- **RSX Macros (`packages/rsx/`, `packages/core-macro/`)**: Custom syntax for declaring UI similar to JSX +- **State Management**: + - **Hooks (`packages/hooks/`)**: React-like state management (use_state, use_memo, use_effect, etc.) + - **Signals (`packages/signals/`)**: Fine-grained reactive state management +- **Component System**: UI components are Rust functions returning `Element` + +### Platform Renderers +- **Web (`packages/web/`)**: WebAssembly renderer for browsers +- **Desktop (`packages/desktop/`)**: Native desktop apps using WebView +- **Mobile (`packages/mobile/`)**: iOS and Android support +- **SSR (`packages/ssr/`)**: Server-side rendering +- **LiveView (`packages/liveview/`)**: Server-driven UI updates + +### Fullstack Web Framework +- **Fullstack (`packages/fullstack/`)**: Complete web framework with SSR and hydration +- **Router (`packages/router/`)**: Type-safe client and server routing +- **Server Functions**: Type-safe RPC between client and server using `#[server]` macro + +### Development Tools +- **CLI (`packages/cli/`)**: `dx` command for building, serving, bundling, and hot-reloading +- **AutoFormat (`packages/autofmt/`)**: RSX code formatter +- **Hot Reloading (`packages/rsx-hotreload/`)**: Live code updates during development + +### Architecture Flow +``` +RSX Components → Virtual DOM → Platform Renderers + ↓ ↓ ↓ +State (Hooks/ Diffing & Web/Desktop/ +Signals) Mutations Mobile/SSR +``` + +## Platform-Specific Development + +### Web (WASM) Development +```bash +# Prerequisites +rustup target add wasm32-unknown-unknown + +# Local development with hot-reloading +dx serve --platform web + +# Production build +dx build --platform web --release +dx bundle --platform web + +# Testing +cargo test -p dioxus-web +cd packages/playwright-tests && npx playwright test +``` + +### Desktop Development +```bash +# Local development +dx serve --platform desktop +# or run examples directly +cargo run --example calculator + +# Production packaging +dx bundle --platform desktop --release + +# Testing +cargo test -p dioxus-desktop +``` + +### SSR/Fullstack Development +```bash +# Run fullstack examples +cargo run -p fullstack-hello-world + +# Testing server functions and routing +cargo test -p dioxus-fullstack +cargo test -p dioxus-router +``` + +### Mobile Development +```bash +# Prerequisites: Android SDK or Xcode installed +# Build for mobile platforms +dx build --platform mobile + +# Run on device/emulator +dx serve --platform android +dx serve --platform ios +``` + +## Key Files and Information + +### Configuration Files +- `Cargo.toml`: Workspace configuration with 70+ packages +- `Dioxus.toml`: Project-specific configuration for dx CLI +- `.github/workflows/main.yml`: CI pipeline defining canonical commands + +### Important Packages +- `packages/dioxus/`: Main umbrella crate with feature flags +- `packages/core/`: Virtual DOM implementation +- `packages/rsx/`: RSX macro parser and implementation +- `packages/cli/`: Dioxus CLI tool source +- `packages/web/`, `packages/desktop/`, `packages/mobile/`: Platform renderers +- `packages/fullstack/`: Web framework with SSR +- `packages/router/`: Type-safe routing + +### Development Version +- Current development version: `0.7.0-alpha.2` +- Minimum Supported Rust Version (MSRV): `1.80.0` + +## Examples and Learning + +The repository contains extensive examples: +- `examples/`: 50+ examples covering all features +- `example-projects/`: Full applications (e.g., HackerNews clone, file explorer) +- Run any example: `cargo run --example ` +- Web examples: `dx serve --example --platform web -- --no-default-features` + +## Troubleshooting + +### Common Issues +- **Build failures**: Ensure all targets installed with `rustup target add wasm32-unknown-unknown` +- **Playwright test failures**: Install browsers with `npx playwright install --with-deps` +- **Linux dependencies**: Install webkit dependencies: + ```bash + sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev + ``` +- **Cache issues**: Clear with `cargo clean` and `rm -rf target/dx` + +### Platform-Specific Notes +- **macOS**: Requires Xcode for native dependencies +- **Windows**: May need Visual Studio Build Tools +- **Linux**: Additional system dependencies required (see CI configuration) + +## Testing Strategy + +- **Unit tests**: `cargo test --workspace` (most packages) +- **Integration tests**: Playwright tests for web functionality +- **Cross-platform**: CI tests on Ubuntu, macOS, and Windows +- **Multiple Rust versions**: Stable, beta, and MSRV testing +- **Platform matrix**: Tests desktop, web, mobile, and server targets + +The project uses a comprehensive CI pipeline that tests across multiple platforms, Rust versions, and feature combinations to ensure compatibility and stability. diff --git a/dx.md b/dx.md new file mode 100644 index 0000000000..cbb5dad204 --- /dev/null +++ b/dx.md @@ -0,0 +1,438 @@ +# dx.md - Dioxus CLI Build System Deep Dive + +This document provides an in-depth analysis of how the `dx` command line tool works, with a focus on integration points and customization opportunities for WASM processing, instrumentation, and build pipeline modifications. + +## Architecture Overview + +The dx CLI is built around a multi-stage build pipeline that handles cross-platform compilation, asset processing, and bundling. The architecture is modular and provides several extension points for custom processing. + +### Core Components + +1. **Build Request System** (`src/build/request.rs`) - Orchestrates the entire build process +2. **Builder Engine** (`src/build/builder.rs`) - Manages build state and progress tracking +3. **Bundle System** (`src/build/bundle.rs`) - Handles platform-specific packaging and optimization +4. **Asset Management** - Processes and optimizes assets through the build pipeline +5. **Platform Adapters** - Web, Desktop, Mobile, and Server-specific build logic + +### Build Pipeline Stages + +```mermaid +graph TD + A[Build Request] --> B[Tooling Verification] + B --> C[Rust Compilation] + C --> D[WASM Processing] + D --> E[Asset Collection] + E --> F[Bundling] + F --> G[Optimization] + G --> H[Platform Assembly] + + D --> D1[wasm-bindgen] + D1 --> D2[wasm-opt] + + E --> E1[Asset Discovery] + E1 --> E2[Asset Processing] + E2 --> E3[Asset Optimization] +``` + +## WASM Processing Pipeline + +The WASM processing is handled in several stages, each providing opportunities for customization: + +### 1. Rust to WASM Compilation + +**Location**: `BuildRequest::build_cargo()` in `src/build/request.rs` + +The compilation uses standard `cargo rustc` with the `wasm32-unknown-unknown` target: + +```rust +// Key compilation arguments for WASM +cargo_args.extend([ + "--target", "wasm32-unknown-unknown", + "--profile", profile_name, + // ... other args +]); +``` + +**Integration Points**: +- **Custom RUSTFLAGS**: Set via environment variables in `BuildRequest::env_vars()` +- **Custom Cargo Features**: Controlled via `target_features()` method +- **Build Scripts**: Standard Cargo build scripts can be used for pre-compilation processing + +### 2. WASM-Bindgen Processing + +**Location**: `AppBundle::run_wasm_bindgen()` in `src/build/bundle.rs` + +This stage converts the raw WASM into web-compatible JavaScript bindings: + +```rust +WasmBindgen::new(&bindgen_version) + .input_path(&input_path) + .target("web") + .debug(keep_debug) + .demangle(keep_debug) + .keep_debug(keep_debug) + .remove_name_section(!keep_debug) + .remove_producers_section(!keep_debug) + .out_name(&name) + .out_dir(&bindgen_outdir) + .run() + .await +``` + +**Integration Points**: +- **Custom wasm-bindgen Version**: Controlled via `krate.wasm_bindgen_version()` +- **Debug Symbol Control**: Via `debug_symbols` build flag +- **Output Customization**: Custom output directories and naming + +### 3. WASM Optimization + +**Location**: `AppBundle::run_wasm_opt()` in `src/build/bundle.rs` + +Uses wasm-opt for size and performance optimizations: + +```rust +#[cfg(feature = "optimizations")] +let mut options = match self.build.krate.config.web.wasm_opt.level { + WasmOptLevel::Z => wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively(), + WasmOptLevel::S => wasm_opt::OptimizationOptions::new_optimize_for_size(), + // ... other levels +}; +``` + +**Configuration**: Controlled via `Dioxus.toml`: +```toml +[web.wasm_opt] +level = "z" # z, s, 0, 1, 2, 3, 4 +debug = false +``` + +## Integration and Customization Points + +### 1. Custom Linker Integration + +The dx CLI can act as a custom linker to intercept the linking process: + +**Location**: `src/cli/link.rs` + +```rust +pub enum LinkAction { + BuildAssetManifest { destination: PathBuf }, + LinkAndroid { linker: PathBuf, extra_flags: Vec }, +} +``` + +**Usage Example**: +```bash +# Set dx as the linker +RUSTFLAGS="-Clinker=$(which dx)" cargo build --target wasm32-unknown-unknown +# dx detects it's being used as a linker via the dx_magic_link_file env var +``` + +### 2. Asset Processing Pipeline + +**Location**: `AppBundle::write_assets()` in `src/build/bundle.rs` + +Assets are processed through the manganis system and can be customized: + +```rust +// Custom asset processing happens here +for (from, to, options) in assets_to_transfer.iter() { + let res = process_file_to(options, from, to); + // Custom processing can be injected here +} +``` + +**Integration Points**: +- **Custom Asset Options**: Via manganis `AssetOptions` +- **Pre/Post Processing**: Custom scripts can be run via build.rs +- **Asset Discovery**: Custom asset discovery through the `DEEPLINK` environment variable + +### 3. Build Script Hooks + +The CLI respects standard Cargo build scripts, providing several hook opportunities: + +**Environment Variables Available in build.rs**: +- `ASSET_ROOT_ENV`: Base path for assets +- `APP_TITLE_ENV`: Application title +- Custom variables from `BuildRequest::env_vars()` + +### 4. Configuration-Based Customization + +**Location**: `src/config/dioxus_config.rs` + +The `Dioxus.toml` configuration provides extensive customization: + +```toml +[application] +# Custom asset directory +asset_dir = "custom_assets" +# Custom output directory +out_dir = "custom_out" + +[web.wasm_opt] +# WASM optimization level +level = "z" +# Debug information retention +debug = false + +[web.resource] +# Custom scripts and styles +style = ["custom.css"] +script = ["custom.js"] +``` + +### 5. Platform-Specific Extensions + +Each platform has specific extension points: + +**Web Platform** (`src/build/web.rs`): +- Custom HTML templates (place `index.html` in crate root) +- Custom resource injection +- Base path configuration + +**Mobile Platform**: +- Android NDK integration +- Custom manifest generation +- Gradle build customization + +## Advanced Integration Patterns + +### 1. Custom WASM Post-Processing + +To add custom WASM processing after wasm-bindgen but before optimization: + +```rust +// In a custom build script or tool +impl AppBundle { + pub async fn custom_wasm_processing(&self) -> Result<()> { + let wasm_file = self.build.wasm_bindgen_wasm_output_file(); + + // Your custom WASM processing here + // - Instrumentation injection + // - Custom optimization passes + // - Security transformations + // - etc. + + Ok(()) + } +} +``` + +### 2. Asset Pipeline Extensions + +Custom asset processing can be injected at multiple points: + +```rust +// Custom asset processor +pub fn custom_asset_processor(options: &AssetOptions, from: &Path, to: &Path) -> Result<()> { + match options { + AssetOptions::Image(_) => { + // Custom image optimization + } + AssetOptions::Css(_) => { + // Custom CSS processing (PostCSS, etc.) + } + AssetOptions::Js(_) => { + // Custom JavaScript processing (minification, etc.) + } + _ => { + // Default processing + std::fs::copy(from, to)?; + } + } + Ok(()) +} +``` + +### 3. Build Event Hooks + +The build system emits events that can be captured: + +```rust +// Build event listener +impl Builder { + pub async fn with_custom_hooks(mut self) -> Self { + // Subscribe to build events + while let Some(update) = self.rx.next().await { + match update { + BuildUpdate::Progress { stage } => { + // Handle build progress + self.handle_build_stage(&stage).await; + } + BuildUpdate::CompilerMessage { message } => { + // Handle compiler messages + self.handle_compiler_message(&message).await; + } + _ => {} + } + } + self + } +} +``` + +## Environment Variables and Configuration + +### Build-Time Environment Variables + +| Variable | Purpose | Example | +|----------|---------|---------| +| `RUSTFLAGS` | Custom Rust compiler flags | `-Clinker=custom-linker` | +| `CARGO_TARGET_DIR` | Custom target directory | `/tmp/custom-target` | +| `dx_magic_link_file` | Linker interception | JSON configuration | +| `DEEPLINK` | Enable deep asset linking | `1` | +| `ASSET_ROOT_ENV` | Asset root path | `/assets/` | +| `APP_TITLE_ENV` | Application title | `My App` | + +### Configuration Precedence + +1. Command line arguments (`--flag value`) +2. Environment variables +3. `Dioxus.toml` configuration +4. Default values + +## Custom Tool Integration Examples + +### 1. WebAssembly Instrumentation + +```bash +#!/bin/bash +# Custom WASM instrumentation script + +# Build with dx +dx build --platform web --release + +# Find the WASM file +WASM_FILE=$(find target/dx -name "*.wasm" | head -1) + +# Apply custom instrumentation +my-wasm-instrument --input "$WASM_FILE" --output "$WASM_FILE.instrumented" +mv "$WASM_FILE.instrumented" "$WASM_FILE" + +# Continue with bundling +dx bundle --platform web +``` + +### 2. Custom Asset Processing + +```rust +// build.rs +use std::process::Command; + +fn main() { + // Run custom asset processing + Command::new("my-asset-processor") + .arg("--input") + .arg("src/assets") + .arg("--output") + .arg("target/processed-assets") + .status() + .expect("Failed to run custom asset processor"); + + println!("cargo:rustc-env=PROCESSED_ASSETS_DIR=target/processed-assets"); +} +``` + +### 3. Integration with External Build Systems + +```yaml +# GitHub Actions integration +name: Build with Custom Processing +on: [push] + +jobs: + build: + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + + - name: Install dx + run: cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli + + - name: Custom pre-processing + run: ./scripts/preprocess.sh + + - name: Build with dx + run: dx build --platform web --release + + - name: Custom post-processing + run: ./scripts/postprocess.sh + + - name: Bundle + run: dx bundle --platform web +``` + +## Debugging and Introspection + +### Build Pipeline Debugging + +```bash +# Enable verbose output +dx build --verbose + +# Enable tracing +dx build --trace + +# JSON output for tooling +dx build --json-output +``` + +### Asset Discovery Debugging + +```bash +# Enable deep linking for asset debugging +DEEPLINK=1 dx build --platform web +``` + +### WASM Analysis + +```bash +# Analyze WASM output +wasm-objdump -h target/dx/web/public/assets/*.wasm +wasm-objdump -s target/dx/web/public/assets/*.wasm + +# Size analysis +wasm-opt --print-stack-ir target/dx/web/public/assets/*.wasm +``` + +## Performance Considerations + +### Build Performance + +- **Incremental Builds**: dx supports incremental compilation through Cargo +- **Parallel Processing**: Assets are processed in parallel using rayon +- **Caching**: Build artifacts are cached between runs +- **Profile Optimization**: Different profiles for dev/release builds + +### WASM Size Optimization + +1. **Optimization Levels**: Configure via `web.wasm_opt.level` +2. **Debug Symbol Stripping**: Control via `debug_symbols` flag +3. **Dead Code Elimination**: Automatic through wasm-opt +4. **Custom Optimization Passes**: Via custom wasm-opt configuration + +## Security Considerations + +When integrating custom tools into the build pipeline, consider: + +1. **Input Validation**: Validate all external inputs +2. **Sandboxing**: Run custom tools in isolated environments +3. **Dependency Verification**: Verify the integrity of external tools +4. **Output Validation**: Validate generated artifacts +5. **Secrets Management**: Never expose secrets in build logs + +## Conclusion + +The dx CLI provides a robust and extensible build system with multiple integration points for custom WASM processing, asset handling, and build pipeline modifications. The key extension points include: + +- **Linker Interception**: For deep build process integration +- **Asset Processing Pipeline**: For custom asset transformations +- **Build Script Hooks**: For pre/post-processing steps +- **Configuration System**: For declarative customization +- **Environment Variables**: For runtime behavior modification + +By leveraging these integration points, developers can create sophisticated build workflows while maintaining compatibility with the dx ecosystem. diff --git a/install-dx.sh b/install-dx.sh new file mode 100755 index 0000000000..bdafd65e08 --- /dev/null +++ b/install-dx.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# Install script for fixed dx binary +# Usage: curl -sSL https://raw.githubusercontent.com/akesson/dioxus/fix/custom-dx-build/install-dx.sh | bash +# Or: ./install-dx.sh [VERSION] + +set -euo pipefail + +# Configuration +REPO="akesson/dioxus" +DEFAULT_VERSION="v0.6.3-fix.1" +VERSION=${1:-$DEFAULT_VERSION} + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Logging functions +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } + +# Detect platform and architecture +detect_platform() { + local os arch + + case "$(uname -s)" in + Darwin*) os="apple-darwin" ;; + Linux*) os="unknown-linux-gnu" ;; + CYGWIN*) os="pc-windows-msvc" ;; + MINGW*) os="pc-windows-msvc" ;; + *) error "Unsupported operating system: $(uname -s)" ;; + esac + + case "$(uname -m)" in + x86_64|amd64) arch="x86_64" ;; + arm64|aarch64) arch="aarch64" ;; + *) error "Unsupported architecture: $(uname -m)" ;; + esac + + echo "${arch}-${os}" +} + +# Main installation function +main() { + info "Installing fixed dx binary version $VERSION" + + local target + target=$(detect_platform) + info "Detected platform: $target" + + local url="https://github.com/${REPO}/releases/download/${VERSION}/dx-${target}-${VERSION}.tar.gz" + local install_dir="${HOME}/.local/bin" + + # Create install directory if it doesn't exist + mkdir -p "$install_dir" + + # Add to PATH if not already there + if [[ ":$PATH:" != *":$install_dir:"* ]]; then + info "Adding $install_dir to PATH. Add this to your shell profile:" + info "export PATH=\"$install_dir:\$PATH\"" + export PATH="$install_dir:$PATH" + fi + + # Download and install + info "Downloading from: $url" + + local temp_dir + temp_dir=$(mktemp -d) + + # Download and extract + if command -v curl &> /dev/null; then + curl -L "$url" | tar -xz -C "$temp_dir" + elif command -v wget &> /dev/null; then + wget -O- "$url" | tar -xz -C "$temp_dir" + else + error "Neither curl nor wget is available" + fi + + # Find the binary (handle different extraction layouts) + local binary_path + if [[ -f "$temp_dir/dx" ]]; then + binary_path="$temp_dir/dx" + elif [[ -f "$temp_dir/dx.exe" ]]; then + binary_path="$temp_dir/dx.exe" + else + error "Could not find dx binary in downloaded archive" + fi + + # Install the binary + cp "$binary_path" "$install_dir/dx" + chmod +x "$install_dir/dx" + + # Cleanup + rm -rf "$temp_dir" + + info "Successfully installed dx to $install_dir/dx" + + # Verify installation + if "$install_dir/dx" --version &> /dev/null; then + info "Installation verified: $("$install_dir/dx" --version)" + info "" + info "🎉 Fixed dx binary installed successfully!" + info "" + info "Usage examples:" + info " dx --version" + info " dx build --platform web --release" + info " dx serve --platform web" + else + warn "Installation completed but dx binary verification failed" + warn "You may need to add $install_dir to your PATH" + fi +} + +# Run main function +main "$@" diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 621a4f6b03..8a1cbf7e02 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -27,7 +27,9 @@ dioxus-dx-wire-format = { workspace = true } clap = { workspace = true, features = ["derive", "cargo"] } convert_case = { workspace = true } thiserror = { workspace = true } -uuid = { version = "1.3.0", features = ["v4"] } +uuid = { version = "1.3.0", features = ["v4", "serde"] } +hex = "0.4.2" +wasmbin = { version = "0.8.1", features = ["exception-handling"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } toml = { workspace = true } @@ -90,6 +92,7 @@ brotli = "6.0.0" ignore = "0.4.22" env_logger = { workspace = true } const-serialize = { workspace = true, features = ["serde"] } +fs-err = "2.11.0" tracing-subscriber = { version = "0.3.18", features = ["std", "env-filter", "json", "registry", "fmt"] } console-subscriber = { version = "0.3.0", optional = true } diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index 7e413a453a..8900ceacc6 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -5,15 +5,32 @@ use crate::{BuildRequest, Platform}; use crate::{Result, TraceSrc}; use anyhow::Context; use dioxus_cli_opt::{process_file_to, AssetManifest}; +use fs_err as fs; use manganis::{AssetOptions, JsAssetOptions}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use std::collections::HashSet; use std::future::Future; +use std::io::{BufReader, BufWriter}; use std::path::{Path, PathBuf}; use std::pin::Pin; use std::sync::atomic::Ordering; use std::{sync::atomic::AtomicUsize, time::Duration}; use tokio::process::Command; +use uuid::Uuid; +use wasmbin::sections::{CustomSection, Section}; +use wasmbin::Module; + +fn as_custom_section(section: &Section) -> Option<&CustomSection> { + section.try_as()?.try_contents().ok() +} + +/// Returns `true` if this section should be stripped. +fn is_strippable_section(section: &Section, strip_names: bool) -> bool { + as_custom_section(section).is_some_and(|section| match section { + CustomSection::Name(_) => strip_names, + other => other.name().starts_with(".debug_"), + }) +} /// The end result of a build. /// @@ -284,6 +301,17 @@ impl AppBundle { .await .context("Failed to write assets")?; bundle.write_metadata().await?; + + // Process WASM with wasm-split: add build ID and split debug info before compression + if let Platform::Web = bundle.build.build.platform() { + // Find the final WASM asset path from the asset manifest + let wasm_bindgen_output = bundle.build.wasm_bindgen_wasm_output_file(); + if let Some(bundled_asset) = bundle.app.assets.assets.get(&wasm_bindgen_output) { + let final_wasm_path = bundle.build.asset_dir().join(bundled_asset.bundled_path()); + bundle.process_wasm_split(&final_wasm_path).await?; + } + } + bundle.optimize().await?; bundle.pre_render_ssg_routes().await?; bundle @@ -329,17 +357,19 @@ impl AppBundle { // Run wasm-bindgen and drop its output into the assets folder under "dioxus" self.build.status_wasm_bindgen_start(); self.run_wasm_bindgen(&self.app.exe.with_extension("wasm")) - .await?; + .await + .context("run wasmbindgen")?; // Only run wasm-opt if the feature is enabled // Wasm-opt has an expensive build script that makes it annoying to keep enabled for iterative dev // We put it behind the "wasm-opt" feature flag so that it can be disabled when iterating on the cli - self.run_wasm_opt(&self.build.exe_dir())?; + self.run_wasm_opt(&self.build.wasm_bindgen_out_dir()) + .context("run wasm-opt")?; // Write the index.html file with the pre-configured contents we got from pre-rendering - std::fs::write( + fs::write( self.build.root_dir().join("index.html"), - self.prepare_html()?, + self.prepare_html().context("prepare html")?, )?; } @@ -362,7 +392,7 @@ impl AppBundle { | Platform::Ios | Platform::Liveview | Platform::Server => { - std::fs::copy(&self.app.exe, self.main_exe())?; + fs::copy(&self.app.exe, self.main_exe())?; } } @@ -394,7 +424,7 @@ impl AppBundle { // the asset was processed. If that version doesn't match the CLI version, we need to re-optimize // all assets. let version_file = self.build.asset_optimizer_version_file(); - let clear_cache = std::fs::read_to_string(&version_file) + let clear_cache = fs::read_to_string(&version_file) .ok() .filter(|s| s == crate::VERSION.as_str()) .is_none(); @@ -415,7 +445,7 @@ impl AppBundle { } // Otherwise, if it is a directory, we need to walk it and remove child files if path.is_dir() { - for entry in std::fs::read_dir(path)?.flatten() { + for entry in fs::read_dir(path)?.flatten() { let path = entry.path(); remove_old_assets(&path, keep_bundled_output_paths).await?; } @@ -441,6 +471,11 @@ impl AppBundle { for (asset, bundled) in &self.app.assets.assets { let from = asset.clone(); let to = asset_dir.join(bundled.bundled_path()); + + // Force recopy WASM files when debug symbols are enabled + if self.build.build.debug_symbols && to.extension().map_or(false, |ext| ext == "wasm") { + let _ = fs::remove_file(&to); // Remove existing file to force recopy + } tracing::debug!("Copying asset {from:?} to {to:?}"); assets_to_transfer.push((from, to, *bundled.options())); } @@ -487,10 +522,10 @@ impl AppBundle { .map_err(|e| anyhow::anyhow!("A task failed while trying to copy assets: {e}"))??; // Remove the wasm bindgen output directory if it exists - _ = std::fs::remove_dir_all(self.build.wasm_bindgen_out_dir()); + _ = fs::remove_dir_all(self.build.wasm_bindgen_out_dir()); // Write the version file so we know what version of the optimizer we used - std::fs::write( + fs::write( self.build.asset_optimizer_version_file(), crate::VERSION.as_str(), )?; @@ -513,14 +548,14 @@ impl AppBundle { .server_exe() .expect("server should be set if we're building a server"); - std::fs::create_dir_all(self.server_exe().unwrap().parent().unwrap())?; + 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)?; + _ = fs::remove_file(&to); + fs::copy(&server.exe, to)?; } Ok(()) @@ -533,13 +568,13 @@ impl AppBundle { Platform::MacOS => { let dest = self.build.root_dir().join("Contents").join("Info.plist"); let plist = self.macos_plist_contents()?; - std::fs::write(dest, plist)?; + fs::write(dest, plist)?; } Platform::Ios => { let dest = self.build.root_dir().join("Info.plist"); let plist = self.ios_plist_contents()?; - std::fs::write(dest, plist)?; + fs::write(dest, plist)?; } // AndroidManifest.xml @@ -619,14 +654,14 @@ impl AppBundle { let input_path = input_path.to_path_buf(); // Make sure the bindgen output directory exists let bindgen_outdir = self.build.wasm_bindgen_out_dir(); - std::fs::create_dir_all(&bindgen_outdir)?; + fs::create_dir_all(&bindgen_outdir)?; let name = self.build.krate.executable_name().to_string(); let keep_debug = - // if we're in debug mode, or we're generating debug symbols, keep debug info - (self.build.krate.config.web.wasm_opt.debug || self.build.build.debug_symbols) - // but only if we're not in release mode - && !self.build.build.release; + // Always keep debug info if --debug-symbols is explicitly enabled + self.build.build.debug_symbols + // Or if we're in debug mode and debug is configured + || (self.build.krate.config.web.wasm_opt.debug && !self.build.build.release); let start = std::time::Instant::now(); @@ -685,12 +720,22 @@ impl AppBundle { }; self.build.status_optimizing_wasm(); + #[cfg(not(feature = "optimizations"))] + { + tracing::info!(dx_src = ?TraceSrc::Build, "Skipping optimization with wasm-opt..."); + } + #[cfg(feature = "optimizations")] { use crate::config::WasmOptLevel; tracing::info!(dx_src = ?TraceSrc::Build, "Running optimization with wasm-opt..."); + // Always keep debug info if --debug-symbols is explicitly enabled + // Otherwise respect the config setting only in debug builds + let keep_debug = self.build.build.debug_symbols + || (self.build.krate.config.web.wasm_opt.debug && !self.build.build.release); + let mut options = match self.build.krate.config.web.wasm_opt.level { WasmOptLevel::Z => { wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively() @@ -704,13 +749,36 @@ impl AppBundle { }; let wasm_file = bindgen_outdir.join(format!("{}_bg.wasm", self.build.krate.executable_name())); - let old_size = wasm_file.metadata()?.len(); - options + let old_size = wasm_file + .metadata() + .with_context(|| format!("Failed to get metadata for {}", wasm_file.display()))? + .len(); + + // Use a temporary output file to avoid in-place modification issues + let temp_wasm_file = wasm_file.with_extension("wasm.tmp"); + tracing::debug!( + "wasm-opt input: {:?}, output: {:?}, keep_debug: {}", + wasm_file, + temp_wasm_file, + keep_debug + ); + + let result = options // WASM bindgen relies on reference types .enable_feature(wasm_opt::Feature::ReferenceTypes) - .debug_info(self.build.krate.config.web.wasm_opt.debug) - .run(&wasm_file, &wasm_file) - .map_err(|err| crate::Error::Other(anyhow::anyhow!(err)))?; + .debug_info(keep_debug) + .run(&wasm_file, &temp_wasm_file); + + if let Err(ref err) = result { + tracing::error!("wasm-opt failed: {:?}", err); + tracing::debug!("Input file exists: {}", wasm_file.exists()); + tracing::debug!("Temp file exists: {}", temp_wasm_file.exists()); + } + + result.map_err(|err| crate::Error::Other(anyhow::anyhow!(err)))?; + + // Move the temporary file back to the original location + fs::rename(&temp_wasm_file, &wasm_file)?; let new_size = wasm_file.metadata()?.len(); tracing::debug!( @@ -821,7 +889,7 @@ impl AppBundle { self.build.build.target_args.arch() )); - std::fs::rename(from, &to).context("Failed to rename aab")?; + fs::rename(from, &to).context("Failed to rename .aab")?; Ok(to) } @@ -831,7 +899,7 @@ impl AppBundle { #[cfg(unix)] { use std::os::unix::prelude::PermissionsExt; - std::fs::set_permissions( + fs::set_permissions( self.build.root_dir().join("gradlew"), std::fs::Permissions::from_mode(0o755), )?; @@ -863,7 +931,72 @@ impl AppBundle { // // https://github.com/rust-mobile/xbuild/blob/master/xbuild/template/lib.rs // https://github.com/rust-mobile/xbuild/blob/master/apk/src/lib.rs#L19 - std::fs::copy(source, destination)?; + fs::copy(source, destination)?; + Ok(()) + } + + /// Process WASM file with wasm-split functionality: add build ID and optionally split debug info + async fn process_wasm_split(&self, wasm_path: &Path) -> Result<()> { + if self.build.build.platform() != Platform::Web || !self.build.build.debug_symbols { + return Ok(()); + } + + tracing::info!( + "Processing WASM file with wasm-split: {}", + wasm_path.display() + ); + + let mut module = Module::decode_from(BufReader::new(fs::File::open(wasm_path)?)) + .map_err(|e| anyhow::anyhow!("Failed to decode WASM module: {}", e))?; + + // Try to see if we already have a build ID. If not, create one. + let has_build_id = module + .sections + .iter() + .filter_map(as_custom_section) + .any(|section| match section { + CustomSection::BuildId(_) => true, + _ => false, + }); + if has_build_id { + // if there was already a build ID, then it was added at a previous build + // and there were no changes to the wasm this build. + // a bit hacky... + return Ok(()); + } + let build_id = Uuid::new_v4().as_bytes().to_vec(); + module + .sections + .push(CustomSection::BuildId(build_id.clone()).into()); + + let debug_file = wasm_path.with_extension("debug.wasm"); + // Write the complete module (including debug info) to the debug file + module + .encode_into(BufWriter::new(fs::File::create(&debug_file)?)) + .map_err(|e| anyhow::anyhow!("Failed to encode debug WASM file: {}", e))?; + tracing::debug!("Created debug companion file: {}", debug_file.display()); + + // Strip debug sections from main file if we created a debug companion + module + .sections + .retain(|section| !is_strippable_section(section, false)); + + // Add external debug info reference if we have a debug file + if let Some(debug_file_name) = debug_file.file_name().and_then(|n| n.to_str()) { + module + .sections + .push(CustomSection::ExternalDebugInfo(debug_file_name.to_string().into()).into()); + } + + // Write the main module + module + .encode_into(BufWriter::new(fs::File::create(wasm_path)?)) + .map_err(|e| anyhow::anyhow!("Failed to encode main WASM file: {}", e))?; + + tracing::debug!( + "WASM processing complete, build ID: {}", + hex::encode(build_id) + ); Ok(()) } } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 6183429d63..9448fdbbab 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -11,6 +11,7 @@ use std::{ process::Stdio, time::Instant, }; +use fs_err as fs; use tokio::{io::AsyncBufReadExt, process::Command}; #[derive(Clone, Debug)] @@ -591,16 +592,15 @@ impl BuildRequest { /// It's not guaranteed that they're different from any other folder fn prepare_build_dir(&self) -> Result<()> { use once_cell::sync::OnceCell; - use std::fs::{create_dir_all, remove_dir_all}; static INITIALIZED: OnceCell> = OnceCell::new(); let success = INITIALIZED.get_or_init(|| { - _ = remove_dir_all(self.exe_dir()); + _ = fs::remove_dir_all(self.exe_dir()); - create_dir_all(self.root_dir())?; - create_dir_all(self.exe_dir())?; - create_dir_all(self.asset_dir())?; + fs::create_dir_all(self.root_dir())?; + fs::create_dir_all(self.exe_dir())?; + fs::create_dir_all(self.asset_dir())?; tracing::debug!("Initialized Root dir: {:?}", self.root_dir()); tracing::debug!("Initialized Exe dir: {:?}", self.exe_dir()); @@ -762,12 +762,11 @@ impl BuildRequest { } fn build_android_app_dir(&self) -> Result<()> { - use std::fs::{create_dir_all, write}; let root = self.root_dir(); // gradle let wrapper = root.join("gradle").join("wrapper"); - create_dir_all(&wrapper)?; + fs::create_dir_all(&wrapper)?; tracing::debug!("Initialized Gradle wrapper: {:?}", wrapper); // app @@ -777,12 +776,12 @@ impl BuildRequest { let app_jnilibs = app_main.join("jniLibs"); let app_assets = app_main.join("assets"); let app_kotlin_out = self.wry_android_kotlin_files_out_dir(); - create_dir_all(&app)?; - create_dir_all(&app_main)?; - create_dir_all(&app_kotlin)?; - create_dir_all(&app_jnilibs)?; - create_dir_all(&app_assets)?; - create_dir_all(&app_kotlin_out)?; + fs::create_dir_all(&app)?; + fs::create_dir_all(&app_main)?; + fs::create_dir_all(&app_kotlin)?; + fs::create_dir_all(&app_jnilibs)?; + fs::create_dir_all(&app_assets)?; + fs::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); @@ -803,50 +802,50 @@ impl BuildRequest { }; // Top-level gradle config - write( + fs::write( root.join("build.gradle.kts"), include_bytes!("../../assets/android/gen/build.gradle.kts"), )?; - write( + fs::write( root.join("gradle.properties"), include_bytes!("../../assets/android/gen/gradle.properties"), )?; - write( + fs::write( root.join("gradlew"), include_bytes!("../../assets/android/gen/gradlew"), )?; - write( + fs::write( root.join("gradlew.bat"), include_bytes!("../../assets/android/gen/gradlew.bat"), )?; - write( + fs::write( root.join("settings.gradle"), include_bytes!("../../assets/android/gen/settings.gradle"), )?; // Then the wrapper and its properties - write( + fs::write( wrapper.join("gradle-wrapper.properties"), include_bytes!("../../assets/android/gen/gradle/wrapper/gradle-wrapper.properties"), )?; - write( + fs::write( wrapper.join("gradle-wrapper.jar"), include_bytes!("../../assets/android/gen/gradle/wrapper/gradle-wrapper.jar"), )?; // Now the app directory - write( + fs::write( app.join("build.gradle.kts"), hbs.render_template( include_str!("../../assets/android/gen/app/build.gradle.kts.hbs"), &hbs_data, )?, )?; - write( + fs::write( app.join("proguard-rules.pro"), include_bytes!("../../assets/android/gen/app/proguard-rules.pro"), )?; - write( + fs::write( app.join("src").join("main").join("AndroidManifest.xml"), hbs.render_template( include_str!("../../assets/android/gen/app/src/main/AndroidManifest.xml.hbs"), @@ -855,7 +854,7 @@ impl BuildRequest { )?; // Write the main activity manually since tao dropped support for it - write( + fs::write( self.wry_android_kotlin_files_out_dir() .join("MainActivity.kt"), hbs.render_template( @@ -866,75 +865,75 @@ impl BuildRequest { // Write the res folder let res = app_main.join("res"); - create_dir_all(&res)?; - create_dir_all(res.join("values"))?; - write( + fs::create_dir_all(&res)?; + fs::create_dir_all(res.join("values"))?; + fs::write( res.join("values").join("strings.xml"), hbs.render_template( include_str!("../../assets/android/gen/app/src/main/res/values/strings.xml.hbs"), &hbs_data, )?, )?; - write( + fs::write( res.join("values").join("colors.xml"), include_bytes!("../../assets/android/gen/app/src/main/res/values/colors.xml"), )?; - write( + fs::write( res.join("values").join("styles.xml"), include_bytes!("../../assets/android/gen/app/src/main/res/values/styles.xml"), )?; - create_dir_all(res.join("drawable"))?; - write( + fs::create_dir_all(res.join("drawable"))?; + fs::write( res.join("drawable").join("ic_launcher_background.xml"), include_bytes!( "../../assets/android/gen/app/src/main/res/drawable/ic_launcher_background.xml" ), )?; - create_dir_all(res.join("drawable-v24"))?; - write( + fs::create_dir_all(res.join("drawable-v24"))?; + fs::write( res.join("drawable-v24").join("ic_launcher_foreground.xml"), include_bytes!( "../../assets/android/gen/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" ), )?; - create_dir_all(res.join("mipmap-anydpi-v26"))?; - write( + fs::create_dir_all(res.join("mipmap-anydpi-v26"))?; + fs::write( res.join("mipmap-anydpi-v26").join("ic_launcher.xml"), include_bytes!( "../../assets/android/gen/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" ), )?; - create_dir_all(res.join("mipmap-hdpi"))?; - write( + fs::create_dir_all(res.join("mipmap-hdpi"))?; + fs::write( res.join("mipmap-hdpi").join("ic_launcher.webp"), include_bytes!( "../../assets/android/gen/app/src/main/res/mipmap-hdpi/ic_launcher.webp" ), )?; - create_dir_all(res.join("mipmap-mdpi"))?; - write( + fs::create_dir_all(res.join("mipmap-mdpi"))?; + fs::write( res.join("mipmap-mdpi").join("ic_launcher.webp"), include_bytes!( "../../assets/android/gen/app/src/main/res/mipmap-mdpi/ic_launcher.webp" ), )?; - create_dir_all(res.join("mipmap-xhdpi"))?; - write( + fs::create_dir_all(res.join("mipmap-xhdpi"))?; + fs::write( res.join("mipmap-xhdpi").join("ic_launcher.webp"), include_bytes!( "../../assets/android/gen/app/src/main/res/mipmap-xhdpi/ic_launcher.webp" ), )?; - create_dir_all(res.join("mipmap-xxhdpi"))?; - write( + fs::create_dir_all(res.join("mipmap-xxhdpi"))?; + fs::write( res.join("mipmap-xxhdpi").join("ic_launcher.webp"), include_bytes!( "../../assets/android/gen/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp" ), )?; - create_dir_all(res.join("mipmap-xxxhdpi"))?; - write( + fs::create_dir_all(res.join("mipmap-xxxhdpi"))?; + fs::write( res.join("mipmap-xxxhdpi").join("ic_launcher.webp"), include_bytes!( "../../assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp" diff --git a/packages/cli/src/build/web.rs b/packages/cli/src/build/web.rs index a3fae3c889..342b803866 100644 --- a/packages/cli/src/build/web.rs +++ b/packages/cli/src/build/web.rs @@ -2,6 +2,7 @@ use dioxus_cli_config::format_base_path_meta_element; use manganis::AssetOptions; use crate::error::Result; +use fs_err as fs; use std::fmt::Write; use std::path::{Path, PathBuf}; @@ -15,7 +16,7 @@ impl AppBundle { let mut html = { let crate_root: &Path = &self.build.krate.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)) + fs::read_to_string(custom_html_file).unwrap_or_else(|_| String::from(DEFAULT_HTML)) }; // Inject any resources from the config into the html diff --git a/packages/cli/src/fastfs.rs b/packages/cli/src/fastfs.rs index cce66bd714..fe24a32849 100644 --- a/packages/cli/src/fastfs.rs +++ b/packages/cli/src/fastfs.rs @@ -5,6 +5,7 @@ use std::{ ffi::OsString, + io::BufWriter, path::{Path, PathBuf}, }; @@ -36,8 +37,10 @@ pub(crate) fn pre_compress_file(path: &Path) -> std::io::Result<()> { let file = std::fs::File::open(path)?; let mut stream = std::io::BufReader::new(file); - let mut buffer = std::fs::File::create(compressed_path)?; - let params = BrotliEncoderParams::default(); + let compressed_file = std::fs::File::create(compressed_path)?; + let mut buffer = BufWriter::new(compressed_file); + let mut params = BrotliEncoderParams::default(); + params.quality = 9; brotli::BrotliCompress(&mut stream, &mut buffer, ¶ms)?; Ok(()) @@ -49,7 +52,13 @@ pub(crate) fn pre_compress_folder(path: &Path, pre_compress: bool) -> std::io::R for entry in walk_dir.into_iter().filter_map(|e| e.ok()) { let entry_path = entry.path(); if entry_path.is_file() { - if pre_compress { + let is_dwarf = entry_path + .file_name() + .map(|n| n.to_str().map(|name| name.ends_with(".debug.wasm"))) + .flatten() + .unwrap_or(false); + + if pre_compress && !is_dwarf { if let Err(err) = pre_compress_file(entry_path) { tracing::error!("Failed to pre-compress file {entry_path:?}: {err}"); }