cli json output, dx bundle fix, dx serve --platform android, race condition, drop ssg platform (#3186)

* clean up logging to avoid random extra trace

* fix race condition with updates

* properly wire up verbose

* fix bundling (macos) and logging

* Add structured output

* clean up clap names

* extract out wire format

* switch structured key to json

* fix random println, fallback to `dioxus/platform`

* clean up logging for run/build

* clean up logging around project

* remove manual exits

* fix tokio runtime for mobile

* rework dog app

* rip out ssg

* Switch dioxus/axum to dioxus/server

* add android template inline

* pre restructure for bundle prep

* add the whole res directory

* Better theme for the app

* remove mobile demo now that most apps work natively

* self-referential android

* only use deep linking for assets

* fix imports for android

* clippy, fixup ios and android

* I'm not boxing compiler message you can't make me

* fix clippy on unix
This commit is contained in:
Jonathan Kelley 2024-11-12 09:01:01 -05:00 committed by GitHub
parent 8a998e7d49
commit ac3e33af46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
116 changed files with 2369 additions and 6093 deletions

4
.gitignore vendored
View file

@ -24,3 +24,7 @@ node_modules/
/packages/playwright-report/ /packages/playwright-report/
/packages/playwright/.cache/ /packages/playwright/.cache/
# ignore the output of tmps
tmp/
bundle/

170
Cargo.lock generated
View file

@ -432,7 +432,7 @@ dependencies = [
"base64ct", "base64ct",
"blake2", "blake2",
"cpufeatures", "cpufeatures",
"password-hash 0.5.0", "password-hash",
] ]
[[package]] [[package]]
@ -558,8 +558,8 @@ dependencies = [
"memchr", "memchr",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"zstd 0.13.2", "zstd",
"zstd-safe 7.2.1", "zstd-safe",
] ]
[[package]] [[package]]
@ -2466,12 +2466,6 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81" checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -3152,7 +3146,6 @@ dependencies = [
name = "dioxus" name = "dioxus"
version = "0.6.0-alpha.4" version = "0.6.0-alpha.4"
dependencies = [ dependencies = [
"axum 0.7.7",
"criterion", "criterion",
"dioxus", "dioxus",
"dioxus-config-macro", "dioxus-config-macro",
@ -3170,7 +3163,6 @@ dependencies = [
"dioxus-router", "dioxus-router",
"dioxus-signals", "dioxus-signals",
"dioxus-ssr", "dioxus-ssr",
"dioxus-static-site-generation",
"dioxus-web", "dioxus-web",
"env_logger 0.10.2", "env_logger 0.10.2",
"futures-util", "futures-util",
@ -3235,6 +3227,7 @@ dependencies = [
"dioxus-core", "dioxus-core",
"dioxus-core-types", "dioxus-core-types",
"dioxus-devtools-types", "dioxus-devtools-types",
"dioxus-dx-wire-format",
"dioxus-fullstack", "dioxus-fullstack",
"dioxus-html", "dioxus-html",
"dioxus-rsx", "dioxus-rsx",
@ -3242,8 +3235,6 @@ dependencies = [
"dioxus-rsx-rosetta", "dioxus-rsx-rosetta",
"dirs", "dirs",
"env_logger 0.11.5", "env_logger 0.11.5",
"flate2",
"fs_extra",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"handlebars", "handlebars",
@ -3253,6 +3244,7 @@ dependencies = [
"hyper-rustls 0.27.3", "hyper-rustls 0.27.3",
"hyper-util", "hyper-util",
"ignore", "ignore",
"include_dir",
"itertools 0.13.0", "itertools 0.13.0",
"krates", "krates",
"log", "log",
@ -3272,7 +3264,6 @@ dependencies = [
"serde_json", "serde_json",
"strum 0.26.3", "strum 0.26.3",
"syn 2.0.87", "syn 2.0.87",
"tar",
"tauri-bundler", "tauri-bundler",
"tauri-utils", "tauri-utils",
"tempfile", "tempfile",
@ -3294,7 +3285,6 @@ dependencies = [
"wasm-bindgen-cli-support", "wasm-bindgen-cli-support",
"wasm-bindgen-shared", "wasm-bindgen-shared",
"wasm-opt", "wasm-opt",
"zip 0.6.6",
] ]
[[package]] [[package]]
@ -3387,8 +3377,12 @@ dependencies = [
"global-hotkey", "global-hotkey",
"http-range", "http-range",
"infer 0.11.0", "infer 0.11.0",
"jni",
"lazy-js-bundle", "lazy-js-bundle",
"muda 0.11.5", "muda 0.11.5",
"ndk",
"ndk-context",
"ndk-sys",
"objc", "objc",
"objc_id", "objc_id",
"once_cell", "once_cell",
@ -3453,6 +3447,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "dioxus-dx-wire-format"
version = "0.6.0-alpha.4"
dependencies = [
"cargo_metadata",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "dioxus-examples" name = "dioxus-examples"
version = "0.6.0-alpha.4" version = "0.6.0-alpha.4"
@ -3844,28 +3847,6 @@ dependencies = [
"rustc-hash 1.1.0", "rustc-hash 1.1.0",
] ]
[[package]]
name = "dioxus-static-site-generation"
version = "0.6.0-alpha.4"
dependencies = [
"axum 0.7.7",
"criterion",
"dioxus",
"dioxus-cli-config",
"dioxus-devtools",
"dioxus-fullstack",
"dioxus-isrg",
"dioxus-lib",
"dioxus-router",
"dioxus-ssr",
"dioxus-web",
"http 1.1.0",
"tokio",
"tower 0.4.13",
"tower-http",
"tracing",
]
[[package]] [[package]]
name = "dioxus-tailwind" name = "dioxus-tailwind"
version = "0.0.0" version = "0.0.0"
@ -5148,15 +5129,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "github-pages-static-generation"
version = "0.1.0"
dependencies = [
"dioxus",
"tower 0.4.13",
"tracing-subscriber",
]
[[package]] [[package]]
name = "gix-actor" name = "gix-actor"
version = "0.31.5" version = "0.31.5"
@ -6321,6 +6293,25 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@ -8192,17 +8183,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]] [[package]]
name = "password-hash" name = "password-hash"
version = "0.5.0" version = "0.5.0"
@ -8244,18 +8224,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash 0.4.2",
"sha2",
]
[[package]] [[package]]
name = "pear" name = "pear"
version = "0.2.9" version = "0.2.9"
@ -9600,14 +9568,6 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "router-static-generation"
version = "0.1.0"
dependencies = [
"dioxus",
"tracing-subscriber",
]
[[package]] [[package]]
name = "rpm" name = "rpm"
version = "0.15.1" version = "0.15.1"
@ -9635,7 +9595,7 @@ dependencies = [
"sha2", "sha2",
"thiserror", "thiserror",
"xz2", "xz2",
"zstd 0.13.2", "zstd",
] ]
[[package]] [[package]]
@ -10435,14 +10395,6 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74" checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74"
[[package]]
name = "simple-static-generation"
version = "0.1.0"
dependencies = [
"dioxus",
"tracing-subscriber",
]
[[package]] [[package]]
name = "simple_asn1" name = "simple_asn1"
version = "0.6.2" version = "0.6.2"
@ -11142,9 +11094,9 @@ dependencies = [
[[package]] [[package]]
name = "tao" name = "tao"
version = "0.30.6" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833b4d43383d76d5078d72f3acd977f47eb5b6751eb40baa665d13828e7b79df" checksum = "2a93f2c6b8fdaeb7f417bda89b5bc767999745c3052969664ae1fa65892deb7e"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"cocoa 0.26.0", "cocoa 0.26.0",
@ -11874,6 +11826,16 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-serde"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
dependencies = [
"serde",
"tracing-core",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.18" version = "0.3.18"
@ -11884,12 +11846,15 @@ dependencies = [
"nu-ansi-term", "nu-ansi-term",
"once_cell", "once_cell",
"regex", "regex",
"serde",
"serde_json",
"sharded-slab", "sharded-slab",
"smallvec", "smallvec",
"thread_local", "thread_local",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-log", "tracing-log",
"tracing-serde",
] ]
[[package]] [[package]]
@ -13642,18 +13607,10 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [ dependencies = [
"aes",
"byteorder", "byteorder",
"bzip2",
"constant_time_eq",
"crc32fast", "crc32fast",
"crossbeam-utils", "crossbeam-utils",
"flate2", "flate2",
"hmac",
"pbkdf2",
"sha1",
"time",
"zstd 0.11.2+zstd.1.5.2",
] ]
[[package]] [[package]]
@ -13698,32 +13655,13 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe 5.0.2+zstd.1.5.2",
]
[[package]] [[package]]
name = "zstd" name = "zstd"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
dependencies = [ dependencies = [
"zstd-safe 7.2.1", "zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
] ]
[[package]] [[package]]

View file

@ -54,18 +54,13 @@ members = [
"packages/server-macro", "packages/server-macro",
"packages/signals", "packages/signals",
"packages/ssr", "packages/ssr",
"packages/static-generation",
"packages/lazy-js-bundle", "packages/lazy-js-bundle",
"packages/cli-config", "packages/cli-config",
"packages/devtools", "packages/devtools",
"packages/devtools-types", "packages/devtools-types",
"packages/isrg", "packages/isrg",
"packages/rsx-hotreload", "packages/rsx-hotreload",
"packages/dx-wire-format",
# Static generation examples
# "packages/static-generation/examples/simple",
# "packages/static-generation/examples/router",
# "packages/static-generation/examples/github-pages",
# Playwright tests # Playwright tests
"packages/playwright-tests/liveview", "packages/playwright-tests/liveview",
@ -94,9 +89,6 @@ members = [
"examples/fullstack-streaming", "examples/fullstack-streaming",
"examples/fullstack-desktop", "examples/fullstack-desktop",
"examples/fullstack-auth", "examples/fullstack-auth",
"examples/ssg-simple",
"examples/ssg-router",
"examples/ssg-github-pages",
# Playwright tests # Playwright tests
"packages/playwright-tests/liveview", "packages/playwright-tests/liveview",
@ -142,8 +134,9 @@ generational-box = { path = "packages/generational-box", version = "0.6.0-alpha.
dioxus-devtools = { path = "packages/devtools", version = "0.6.0-alpha.4" } dioxus-devtools = { path = "packages/devtools", version = "0.6.0-alpha.4" }
dioxus-devtools-types = { path = "packages/devtools-types", version = "0.6.0-alpha.4" } dioxus-devtools-types = { path = "packages/devtools-types", version = "0.6.0-alpha.4" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.6.0-alpha.4" } dioxus-fullstack = { path = "packages/fullstack", version = "0.6.0-alpha.4" }
dioxus-static-site-generation = { path = "packages/static-generation", version = "0.6.0-alpha.4" }
dioxus_server_macro = { path = "packages/server-macro", version = "0.6.0-alpha.4", default-features = false } dioxus_server_macro = { path = "packages/server-macro", version = "0.6.0-alpha.4", default-features = false }
dioxus-dx-wire-format = { path = "packages/dx-wire-format", version = "0.6.0-alpha.4" }
lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.6.0-alpha.4" } lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.6.0-alpha.4" }
manganis = { path = "packages/manganis/manganis", version = "0.6.0-alpha.4" } manganis = { path = "packages/manganis/manganis", version = "0.6.0-alpha.4" }
manganis-core = { path = "packages/manganis/manganis-core", version = "0.6.0-alpha.4" } manganis-core = { path = "packages/manganis/manganis-core", version = "0.6.0-alpha.4" }
@ -222,10 +215,11 @@ dirs = "5.0.1"
cargo-config2 = "0.1.26" cargo-config2 = "0.1.26"
criterion = { version = "0.5" } criterion = { version = "0.5" }
walrus = "*" walrus = "*"
cargo_metadata = "0.18.1"
# desktop # desktop
wry = { version = "0.45.0", default-features = false } wry = { version = "0.45.0", default-features = false }
tao = { version = "0.30.0", features = ["rwh_05"] } tao = { version = "=0.30.0", features = ["rwh_05"] }
webbrowser = "1.0.1" webbrowser = "1.0.1"
infer = "0.16.0" infer = "0.16.0"
dunce = "1.0.2" dunce = "1.0.2"
@ -310,8 +304,8 @@ default = ["desktop"]
desktop = ["dioxus/desktop"] desktop = ["dioxus/desktop"]
liveview = ["dioxus/liveview"] liveview = ["dioxus/liveview"]
fullstack = ["dioxus/fullstack"] fullstack = ["dioxus/fullstack"]
axum = ["dioxus/axum"] server = ["dioxus/server"]
server = ["dioxus/axum"] mobile = ["dioxus/mobile"]
web = ["dioxus/web"] web = ["dioxus/web"]
http = ["dep:reqwest", "dep:http-range"] http = ["dep:reqwest", "dep:http-range"]

View file

@ -18,5 +18,5 @@ tracing-subscriber = "0.3.17"
[features] [features]
default = [] default = []
server = ["dioxus/axum"] server = ["dioxus/server"]
web = ["dioxus/web"] web = ["dioxus/web"]

View file

@ -5,8 +5,6 @@ use async_std::task::sleep;
use dioxus::prelude::*; use dioxus::prelude::*;
use web_time::Instant; use web_time::Instant;
const STYLE: Asset = asset!("/examples/assets/clock.css");
fn main() { fn main() {
dioxus::launch(app); dioxus::launch(app);
} }
@ -36,7 +34,7 @@ fn app() -> Element {
); );
rsx! { rsx! {
document::Link { rel: "stylesheet", href: STYLE } document::Stylesheet { href: asset!("/examples/assets/clock.css") }
div { id: "app", div { id: "app",
div { id: "title", "Carpe diem 🎉" } div { id: "title", "Carpe diem 🎉" }
div { id: "clock-display", "{time}" } div { id: "clock-display", "{time}" }

View file

@ -39,10 +39,8 @@ fn app() -> Element {
rsx! { rsx! {
for cur_breed in breeds.message.keys().take(20).cloned() { for cur_breed in breeds.message.keys().take(20).cloned() {
li { key: "{cur_breed}", button { onclick: move |_| breed.set(cur_breed.clone()),
button { onclick: move |_| breed.set(cur_breed.clone()), "{cur_breed}"
"{cur_breed}"
}
} }
} }
} }
@ -55,11 +53,9 @@ fn app() -> Element {
}; };
rsx! { rsx! {
h1 { "Select a dog breed!" } h1 { "Select a dog breed: {breed}" }
div { height: "500px", display: "flex", BreedPic { breed }
ul { width: "100px", {breed_list} } div { width: "400px", {breed_list} }
div { flex: 1, BreedPic { breed } }
}
} }
} }
@ -81,8 +77,10 @@ fn BreedPic(breed: Signal<String>) -> Element {
match fut.read_unchecked().as_ref() { match fut.read_unchecked().as_ref() {
Some(Ok(resp)) => rsx! { Some(Ok(resp)) => rsx! {
button { onclick: move |_| fut.restart(), "Click to fetch another doggo" } div {
img { max_width: "500px", max_height: "500px", src: "{resp.message}" } button { onclick: move |_| fut.restart(), padding: "5px", background_color: "gray", color: "white", border_radius: "5px", "Click to fetch another doggo" }
img { max_width: "500px", max_height: "500px", src: "{resp.message}" }
}
}, },
Some(Err(_)) => rsx! { "loading image failed" }, Some(Err(_)) => rsx! { "loading image failed" },
None => rsx! { "loading image..." }, None => rsx! { "loading image..." },

View file

@ -14,6 +14,6 @@ serde = { version = "1.0.159", features = ["derive"] }
[features] [features]
default = [] default = []
server = ["axum", "dioxus/axum"] server = ["axum", "dioxus/server"]
web = ["dioxus/web"] web = ["dioxus/web"]

View file

@ -20,5 +20,5 @@ once_cell = "1.19.0"
[features] [features]
default = [] default = []
server = ["dioxus/axum", "dep:tokio"] server = ["dioxus/server", "dep:tokio"]
web = ["dioxus/web"] web = ["dioxus/web"]

View file

@ -1,10 +0,0 @@
# Rust
target/
**/*.rs.bk
# cargo-mobile2
.cargo/
/gen
# macOS
.DS_Store

File diff suppressed because it is too large Load diff

View file

@ -1,51 +0,0 @@
[package]
name = "mobile-demo"
version = "0.1.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2021"
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
[[bin]]
name = "mobile-demo-desktop"
path = "gen/bin/desktop.rs"
[package.metadata.cargo-android]
app-activity-name = "com.example.mobile_demo.MainActivity"
app-dependencies = [
"androidx.webkit:webkit:1.6.1",
"androidx.appcompat:appcompat:1.6.1",
"com.google.android.material:material:1.8.0",
]
project-dependencies = ["org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21"]
app-plugins = ["org.jetbrains.kotlin.android"]
app-permissions = ["android.permission.INTERNET"]
app-theme-parent = "Theme.MaterialComponents.DayNight.DarkActionBar"
vulkan-validation = false
[package.metadata.cargo-android.env-vars]
WRY_ANDROID_PACKAGE = "com.example.mobile_demo"
WRY_ANDROID_LIBRARY = "mobile_demo"
WRY_ANDROID_KOTLIN_FILES_OUT_DIR = "<android-project-dir>/app/src/main/kotlin/com/example/mobile_demo"
[package.metadata.cargo-apple.ios]
frameworks = ["WebKit"]
[dependencies]
anyhow = "1.0.56"
log = "0.4.11"
wry = "0.35.0"
dioxus = { path = "../../packages/dioxus", features = ["mobile"]}
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.9.0"
jni = "0.19.0"
paste = "1.0"
[target.'cfg(not(target_os = "android"))'.dependencies]
env_logger = "0.9.0"
[target.'cfg(target_os = "ios")'.dependencies]
core-foundation = "0.9.3"

View file

@ -1,11 +0,0 @@
# Dioxus Mobile demo
## How this project was generated
Right now, Dioxus supports mobile targets including iOS and Android. However, our tooling is not mature enough to include the build commands directly.
This project was generated using [cargo-mobile2](https://github.com/tauri-apps/cargo-mobile2). We have yet to integrate this generation into the Dioxus-CLI. The open issue for this is [#1157](https://github.com/DioxusLabs/dioxus/issues/1157).
## Running this project
Because the tooling and ecosystem is still young, Dioxus mobile can be difficult to setup and run. We have [detailed guides](https://dioxuslabs.com/learn/0.5/getting_started) for creating, building, and running on both iOS and Android.

View file

@ -1,8 +0,0 @@
[app]
name = "mobile-demo"
stylized-name = "Mobile Demo"
domain = "example.com"
template-pack = "wry"
[apple]
development-team = "34U4FG9TJ8"

View file

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Dioxus app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<!-- CUSTOM HEAD -->
</head>
<body>
<div id="main"></div>
<!-- MODULE LOADER -->
</body>
</html>

View file

@ -1,90 +0,0 @@
use anyhow::Result;
use dioxus::mobile::Config;
use dioxus::prelude::*;
#[cfg(target_os = "android")]
use dioxus::mobile::wry::android_binding;
#[cfg(target_os = "android")]
fn init_logging() {
android_logger::init_once(
android_logger::Config::default()
.with_min_level(log::Level::Trace)
.with_tag("mobile-demo"),
);
}
#[cfg(not(target_os = "android"))]
fn init_logging() {
env_logger::init();
}
#[cfg(any(target_os = "android", target_os = "ios"))]
fn stop_unwind<F: FnOnce() -> T, T>(f: F) -> T {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(t) => t,
Err(err) => {
eprintln!("attempt to unwind out of `rust` with err: {:?}", err);
std::process::abort()
}
}
}
#[cfg(any(target_os = "android", target_os = "ios"))]
fn _start_app() {
stop_unwind(|| main().unwrap());
}
#[no_mangle]
#[inline(never)]
#[cfg(any(target_os = "android", target_os = "ios"))]
pub extern "C" fn start_app() {
#[cfg(target_os = "android")]
android_binding!(com_example, mobile_demo, _start_app);
#[cfg(target_os = "ios")]
_start_app()
}
pub fn main() -> Result<()> {
init_logging();
// Right now we're going through dioxus-desktop but we'd like to go through dioxus-mobile
// That will seed the index.html with some fixes that prevent the page from scrolling/zooming etc
dioxus::LaunchBuilder::mobile()
.with_cfg(
// Note that we have to disable the viewport goofiness of the browser.
// Dioxus_mobile should do this for us
Config::default().with_custom_index(include_str!("index.html").to_string()),
)
.launch(app);
Ok(())
}
fn app() -> Element {
let mut items = use_signal(|| vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
log::debug!("Hello from the app");
rsx! {
div {
h1 { "Hello, Mobile" }
div {
margin_left: "auto",
margin_right: "auto",
width: "200px",
padding: "10px",
border: "1px solid black",
button {
onclick: move |_| {
items.push(items.len());
},
"Add item"
}
for item in items.iter() {
div { "- {item}" }
}
}
}
}
}

View file

@ -1,4 +0,0 @@
dist
target
docs
.dioxus

View file

@ -1,17 +0,0 @@
[package]
name = "github-pages-static-generation"
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, features = ["static-generation", "router"] }
tower = { workspace = true, features = ["util"] }
tracing-subscriber = "0.3.18"
[features]
default = []
server = ["dioxus/axum"]
web = ["dioxus/web"]

View file

@ -1,70 +0,0 @@
//! You can use the `github_pages` method to set up a preset for github pages.
//! This will output your files in the `/docs` directory and set up a `404.html` file.
#![allow(unused)]
use dioxus::prelude::*;
// Generate all routes and output them to the static path
fn main() {
dioxus::LaunchBuilder::new()
.with_cfg(dioxus::static_site_generation::Config::new().github_pages())
.launch(|| {
rsx! {
Router::<Route> {}
}
});
}
#[derive(Clone, Routable, Debug, PartialEq)]
enum Route {
#[route("/")]
Home {},
#[route("/blog")]
Blog,
// You must include a catch all route to handle 404s
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
#[component]
fn Blog() -> Element {
rsx! {
Link { to: Route::Home {}, "Go to counter" }
table {
tbody {
for _ in 0..100 {
tr {
for _ in 0..100 {
td { "hello world!" }
}
}
}
}
}
}
}
#[component]
fn Home() -> Element {
let mut count = use_signal(|| 0);
rsx! {
Link { to: Route::Blog {}, "Go to blog" }
div {
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
}
}
}
#[component]
fn PageNotFound(route: Vec<String>) -> Element {
rsx! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre { color: "red", "log:\nattempted to navigate to: {route:?}" }
}
}

View file

@ -1,4 +0,0 @@
dist
target
static
.dioxus

View file

@ -1,16 +0,0 @@
[package]
name = "router-static-generation"
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, features = ["static-generation", "router"] }
tracing-subscriber = "0.3.18"
[features]
default = []
server = ["dioxus/axum"]
web = ["dioxus/web"]

View file

@ -1,54 +0,0 @@
//! Static generation works out of the box with the router. Just add a router anywhere in your app and it will generate any static routes for you!
#![allow(unused)]
use dioxus::prelude::*;
// Generate all routes and output them to the static path
fn main() {
dioxus::launch(|| {
rsx! {
Router::<Route> {}
}
});
}
#[derive(Clone, Routable, Debug, PartialEq)]
enum Route {
#[route("/")]
Home {},
#[route("/blog")]
Blog,
}
#[component]
fn Blog() -> Element {
rsx! {
Link { to: Route::Home {}, "Go to counter" }
table {
tbody {
for _ in 0..100 {
tr {
for _ in 0..100 {
td { "hello!" }
}
}
}
}
}
}
}
#[component]
fn Home() -> Element {
let mut count = use_signal(|| 0);
rsx! {
Link { to: Route::Blog {}, "Go to blog" }
div {
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
}
}
}

View file

@ -1,4 +0,0 @@
dist
target
static
.dioxus

View file

@ -1,16 +0,0 @@
[package]
name = "simple-static-generation"
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, features = ["static-generation"] }
tracing-subscriber = "0.3.18"
[features]
default = []
server = ["dioxus/axum"]
web = ["dioxus/web"]

View file

@ -1,17 +0,0 @@
//! Static generation lets you pre-render your entire app to static files and then hydrate it on the client.
use dioxus::prelude::*;
// Generate all routes and output them to the static path
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut count = use_signal(|| 0);
rsx! {
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
}
}

View file

@ -16,6 +16,11 @@ pub const OUT_DIR: &str = "DIOXUS_OUT_DIR";
/// ///
/// This is not a websocket! There's no protocol! /// This is not a websocket! There's no protocol!
pub fn devserver_raw_addr() -> Option<SocketAddr> { pub fn devserver_raw_addr() -> Option<SocketAddr> {
// On android, 10.0.2.2 is the default loopback
if cfg!(target_os = "android") {
return Some("10.0.2.2:8080".parse().unwrap());
}
std::env::var(DEVSERVER_RAW_ADDR_ENV) std::env::var(DEVSERVER_RAW_ADDR_ENV)
.map(|s| s.parse().ok()) .map(|s| s.parse().ok())
.ok() .ok()

View file

@ -21,6 +21,7 @@ dioxus-core-types = { workspace = true }
dioxus-devtools-types = { workspace = true } dioxus-devtools-types = { workspace = true }
dioxus-cli-config = { workspace = true } dioxus-cli-config = { workspace = true }
dioxus-fullstack = { workspace = true } dioxus-fullstack = { workspace = true }
dioxus-dx-wire-format = { workspace = true }
clap = { workspace = true, features = ["derive", "cargo"] } clap = { workspace = true, features = ["derive", "cargo"] }
thiserror = { workspace = true } thiserror = { workspace = true }
@ -30,12 +31,11 @@ uuid = { version = "1.3.0", features = ["v4"] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
fs_extra = "1.2.0"
cargo_toml = { workspace = true } cargo_toml = { workspace = true }
futures-util = { workspace = true, features = ["async-await-macro"] } futures-util = { workspace = true, features = ["async-await-macro"] }
notify = { workspace = true, features = ["serde"] } notify = { workspace = true, features = ["serde"] }
html_parser = { workspace = true } html_parser = { workspace = true }
cargo_metadata = "0.18.1" cargo_metadata = { workspace = true }
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"
chrono = "0.4.19" chrono = "0.4.19"
@ -71,9 +71,6 @@ reqwest = { workspace = true, features = [
"trust-dns", "trust-dns",
"blocking", "blocking",
] } ] }
flate2 = "1.0.22"
tar = "0.4.38"
zip = "0.6.2"
tower = { workspace = true } tower = { workspace = true }
once_cell = "1.19.0" once_cell = "1.19.0"
@ -91,7 +88,7 @@ brotli = "6.0.0"
ignore = "0.4.22" ignore = "0.4.22"
env_logger = { workspace = true } env_logger = { workspace = true }
tracing-subscriber = { version = "0.3.18", features = ["std", "env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["std", "env-filter", "json"] }
console-subscriber = { version = "0.3.0", optional = true } console-subscriber = { version = "0.3.0", optional = true }
tracing = { workspace = true } tracing = { workspace = true }
wasm-opt = { version = "0.116.1", optional = true } wasm-opt = { version = "0.116.1", optional = true }
@ -118,6 +115,7 @@ strum = { version = "0.26.3", features = ["derive"] }
tauri-utils = { workspace = true } tauri-utils = { workspace = true }
tauri-bundler = { workspace = true } tauri-bundler = { workspace = true }
include_dir = "0.7.4"
[build-dependencies] [build-dependencies]
built = { version = "=0.7.4", features = ["git2"] } built = { version = "=0.7.4", features = ["git2"] }

View file

@ -1 +0,0 @@
app-dir/

View file

@ -0,0 +1,3 @@
package com.example.androidfinal
class MainActivity : WryActivity()

View file

@ -0,0 +1,15 @@
*.iml
.gradle
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
build
/captures
.externalNativeBuild
jniLibs
.cxx
local.properties

View file

@ -0,0 +1,49 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace="com.example.androidfinal"
compileSdk = 33
defaultConfig {
applicationId = "com.example.androidfinal"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("debug") {
isDebuggable = true
isJniDebuggable = true
isMinifyEnabled = false
packaging {
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
jniLibs.keepDebugSymbols.add("*/x86/*.so")
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
}
}
getByName("release") {
isMinifyEnabled = true
proguardFiles(
*fileTree(".") { include("**/*.pro") }
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray()
)
}
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
buildConfig = true
}
}
dependencies {
implementation("androidx.webkit:webkit:1.6.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application android:hasCode="true" android:supportsRtl="true" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:theme="@style/AppTheme">
<activity android:configChanges="orientation|keyboardHidden" android:exported="true"
android:label="@string/app_name" android:name="com.example.androidfinal.MainActivity">
<meta-data android:name="android.app.lib_name" android:value="androidfinal" />
<meta-data android:name="android.app.func_name" android:value="ANativeActivity_onCreate" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Androidfinal</string>
</resources>

View file

@ -0,0 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -0,0 +1,22 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register("clean").configure {
delete("build")
}

View file

@ -0,0 +1,25 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false

Binary file not shown.

View file

@ -0,0 +1,6 @@
#Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
packages/cli/assets/android/gen/gradlew vendored Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View file

@ -0,0 +1,90 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,2 @@
include ':app'

View file

@ -3,14 +3,18 @@
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleIdentifier</key>
<string>com.dioxuslabs</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>DioxusApp</string> <string>{{ display_name }}</string>
<key>CFBundleName</key>
<string>DioxusApp</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>DioxusApp</string> <string>{{ executable_name }}</string>
<key>CFBundleIdentifier</key>
<string>{{ bundle_identifier }}</string>
<key>CFBundleName</key>
<string>{{ bundle_name }}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>0.1.0</string> <string>0.1.0</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View file

@ -3,23 +3,23 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>DioxusApp</string> <string>{{ display_name }}</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>DioxusApp</string> <string>{{ executable_name }}</string>
<key>CFBundleIdentifier</key>
<string>{{ bundle_identifier }}</string>
<key>CFBundleName</key>
<string>{{ bundle_name }}</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>icon.icns</string> <string>icon.icns</string>
<key>CFBundleIdentifier</key>
<string>org.dioxuslabs.dioxus-desktop</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, Platform, ProgressRx, AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, ProgressRx,
ProgressTx, Result, ProgressTx, Result, StructuredOutput,
}; };
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -40,6 +40,7 @@ impl Builder {
pub(crate) fn start(krate: &DioxusCrate, args: BuildArgs) -> Result<Self> { pub(crate) fn start(krate: &DioxusCrate, args: BuildArgs) -> Result<Self> {
let (tx, rx) = futures_channel::mpsc::unbounded(); 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());
Ok(Self { Ok(Self {
krate: krate.clone(), krate: krate.clone(),
request: request.clone(), request: request.clone(),
@ -49,14 +50,7 @@ impl Builder {
// We wont bother verifying on subsequent builds // We wont bother verifying on subsequent builds
request.verify_tooling().await?; request.verify_tooling().await?;
let res = request.build_all().await; request.build_all().await
// The first launch gets some extra logging :)
if res.is_ok() {
tracing::info!("Build completed successfully, launching app! 💫")
}
res
}), }),
tx, tx,
rx, rx,
@ -91,79 +85,76 @@ impl Builder {
}, },
}; };
tracing::trace!("Build update: {update:?}");
// Update the internal stage of the build so the UI can render it // Update the internal stage of the build so the UI can render it
match &update { match &update {
BuildUpdate::Progress { stage } => { BuildUpdate::Progress { stage } => {
// Prevent updates from flowing in after the build has already finished // Prevent updates from flowing in after the build has already finished
if !self.is_finished() { if !self.is_finished() {
self.stage = stage.clone(); self.stage = stage.clone();
}
match stage { match stage {
BuildStage::Initializing => { BuildStage::Initializing => {
self.compiled_crates = 0; self.compiled_crates = 0;
self.compiled_crates_server = 0; self.compiled_crates_server = 0;
self.bundling_progress = 0.0; self.bundling_progress = 0.0;
}
BuildStage::Starting {
crate_count,
platform,
} => {
if *platform == Platform::Server {
self.expected_crates_server = *crate_count;
} else {
self.expected_crates = *crate_count;
} }
} BuildStage::Starting {
BuildStage::InstallingTooling {} => {} crate_count,
BuildStage::Compiling { is_server,
current, } => {
total, if *is_server {
platform, self.expected_crates_server = *crate_count;
.. } else {
} => { self.expected_crates = *crate_count;
if *platform == Platform::Server { }
self.compiled_crates_server = *current;
self.expected_crates_server = *total;
} else {
self.compiled_crates = *current;
self.expected_crates = *total;
} }
BuildStage::InstallingTooling {} => {}
BuildStage::Compiling {
current,
total,
is_server,
..
} => {
if *is_server {
self.compiled_crates_server = *current;
self.expected_crates_server = *total;
} else {
self.compiled_crates = *current;
self.expected_crates = *total;
}
if self.compile_start.is_none() { if self.compile_start.is_none() {
self.compile_start = Some(Instant::now()); self.compile_start = Some(Instant::now());
}
} }
} BuildStage::Bundling {} => {
BuildStage::Bundling {} => { self.complete_compile();
self.complete_compile(); self.bundling_progress = 0.0;
self.bundling_progress = 0.0; self.bundle_start = Some(Instant::now());
self.bundle_start = Some(Instant::now()); }
} BuildStage::OptimizingWasm {} => {}
BuildStage::OptimizingWasm {} => {} BuildStage::CopyingAssets { current, total, .. } => {
BuildStage::CopyingAssets { current, total, .. } => { self.bundling_progress = *current as f64 / *total as f64;
self.bundling_progress = *current as f64 / *total as f64; }
} BuildStage::Success => {
BuildStage::Success => { self.compiled_crates = self.expected_crates;
self.compiled_crates = self.expected_crates; self.compiled_crates_server = self.expected_crates_server;
self.compiled_crates_server = self.expected_crates_server; self.bundling_progress = 1.0;
self.bundling_progress = 1.0; }
} BuildStage::Failed => {
BuildStage::Failed => { self.compiled_crates = self.expected_crates;
self.compiled_crates = self.expected_crates; self.compiled_crates_server = self.expected_crates_server;
self.compiled_crates_server = self.expected_crates_server; self.bundling_progress = 1.0;
self.bundling_progress = 1.0; }
} BuildStage::Aborted => {}
BuildStage::Aborted => {} BuildStage::Restarting => {
BuildStage::Restarting => { self.compiled_crates = 0;
self.compiled_crates = 0; self.compiled_crates_server = 0;
self.compiled_crates_server = 0; self.expected_crates = 1;
self.expected_crates = 1; self.bundling_progress = 0.0;
self.bundling_progress = 0.0; }
} BuildStage::RunningBindgen {} => {}
BuildStage::RunningBindgen {} => { _ => {}
self.bundling_progress = 0.5;
} }
} }
} }
@ -224,10 +215,43 @@ impl Builder {
pub(crate) async fn finish(&mut self) -> Result<AppBundle> { pub(crate) async fn finish(&mut self) -> Result<AppBundle> {
loop { loop {
match self.wait().await { match self.wait().await {
BuildUpdate::BuildReady { bundle } => return Ok(bundle), BuildUpdate::Progress { stage } => {
BuildUpdate::BuildFailed { err } => return Err(err), match &stage {
BuildUpdate::Progress { .. } => {} BuildStage::Compiling {
BuildUpdate::CompilerMessage { .. } => {} current,
total,
krate,
..
} => {
tracing::info!("Compiling [{current:>3}/{total}]: {krate}");
}
BuildStage::RunningBindgen => tracing::info!("Running wasm-bindgen..."),
BuildStage::CopyingAssets {
current,
total,
path,
} => {
tracing::info!("Copying asset ({current}/{total}): {}", path.display());
}
BuildStage::Bundling => tracing::info!("Bundling app..."),
_ => {}
}
tracing::info!(json = ?StructuredOutput::BuildUpdate { stage: stage.clone() });
}
BuildUpdate::CompilerMessage { message } => {
tracing::info!(json = ?StructuredOutput::CargoOutput { message: message.clone() }, %message);
}
BuildUpdate::BuildReady { bundle } => {
tracing::debug!(json = ?StructuredOutput::BuildFinished {
path: bundle.build.root_dir(),
});
return Ok(bundle);
}
BuildUpdate::BuildFailed { err } => {
tracing::error!(?err, json = ?StructuredOutput::Error { message: err.to_string() });
return Err(err);
}
} }
} }
} }
@ -275,7 +299,7 @@ impl Builder {
self.bundling_progress self.bundling_progress
} }
fn is_finished(&self) -> bool { pub(crate) fn is_finished(&self) -> bool {
match self.stage { match self.stage {
BuildStage::Success => true, BuildStage::Success => true,
BuildStage::Failed => true, BuildStage::Failed => true,

View file

@ -3,13 +3,16 @@ use crate::{assets::AssetManifest, TraceSrc};
use crate::{BuildRequest, Platform}; use crate::{BuildRequest, Platform};
use anyhow::Context; use anyhow::Context;
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use std::sync::atomic::AtomicUsize;
use std::{ use std::{
fs::create_dir_all, fs::create_dir_all,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use std::{sync::atomic::AtomicUsize, time::Duration};
use tokio::process::Command;
use wasm_bindgen_cli_support::Bindgen; use wasm_bindgen_cli_support::Bindgen;
use super::templates::InfoPlistData;
/// The end result of a build. /// The end result of a build.
/// ///
/// Contains the final asset manifest, the executables, and the workdir. /// Contains the final asset manifest, the executables, and the workdir.
@ -93,13 +96,14 @@ pub(crate) struct AppBundle {
pub struct BuildArtifacts { pub struct BuildArtifacts {
pub(crate) exe: PathBuf, pub(crate) exe: PathBuf,
pub(crate) assets: AssetManifest, pub(crate) assets: AssetManifest,
pub(crate) time_taken: Duration,
} }
impl AppBundle { impl AppBundle {
/// ## Web: /// ## Web:
/// Create a folder that is somewhat similar to an app-image (exe + asset) /// 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. /// The server is dropped into the `web` folder, even if there's no `public` folder.
/// If there's no server (SPA/static-gen), we still use the `web` folder, but it only contains the /// If there's no server (SPA), we still use the `web` folder, but it only contains the
/// public folder. /// public folder.
/// ``` /// ```
/// web/ /// web/
@ -255,52 +259,40 @@ impl AppBundle {
/// root().join(bundled) /// root().join(bundled)
/// ``` /// ```
pub(crate) async fn new( pub(crate) async fn new(
request: BuildRequest, build: BuildRequest,
app: BuildArtifacts, app: BuildArtifacts,
server: Option<BuildArtifacts>, server: Option<BuildArtifacts>,
) -> Result<Self> { ) -> Result<Self> {
let bundle = Self { let bundle = Self { app, server, build };
app,
server,
build: request,
};
tracing::debug!("Assembling app bundle"); tracing::debug!("Assembling app bundle");
bundle.build.status_start_bundle(); bundle.build.status_start_bundle();
bundle.prepare_build_dir()?; /*
bundle.write_main_executable().await?; 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
.context("Failed to write main executable")?;
bundle.write_server_executable().await?; bundle.write_server_executable().await?;
bundle.write_assets().await?; bundle
.write_assets()
.await
.context("Failed to write assets")?;
bundle.write_metadata().await?; bundle.write_metadata().await?;
bundle.optimize().await?; bundle.optimize().await?;
bundle
.assemble()
.await
.context("Failed to assemble app bundle")?;
tracing::debug!("Bundle created at {}", bundle.build.root_dir().display());
Ok(bundle) Ok(bundle)
} }
/// We only really currently care about:
///
/// - app dir (.app, .exe, .apk, etc)
/// - assets dir
/// - exe dir (.exe, .app, .apk, etc)
/// - extra scaffolding
///
/// It's not guaranteed that they're different from any other folder
fn prepare_build_dir(&self) -> Result<()> {
_ = std::fs::remove_dir_all(self.app_dir());
create_dir_all(self.app_dir())?;
create_dir_all(self.exe_dir())?;
create_dir_all(self.asset_dir())?;
// we could download the templates from somewhere (github?) but after having banged my head against
// cargo-mobile2 for ages, I give up with that. We're literally just going to hardcode the templates
// by writing them here.
if let Platform::Android = self.build.build.platform() {}
Ok(())
}
/// Take the output of rustc and make it into the main exe of the bundle /// 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 /// For wasm, we'll want to run `wasm-bindgen` to make it a wasm binary along with some other optimizations
@ -333,18 +325,17 @@ impl AppBundle {
Platform::Web => { Platform::Web => {
// Run wasm-bindgen and drop its output into the assets folder under "dioxus" // Run wasm-bindgen and drop its output into the assets folder under "dioxus"
self.build.status_wasm_bindgen_start(); self.build.status_wasm_bindgen_start();
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.exe_dir()) self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.build.exe_dir())
.await?; .await?;
// Only run wasm-opt if the feature is enabled // 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 // 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 // We put it behind the "wasm-opt" feature flag so that it can be disabled when iterating on the cli
self.build.status_wasm_opt_start(); self.run_wasm_opt(&self.build.exe_dir())?;
self.run_wasm_opt(&self.exe_dir())?;
// Write the index.html file with the pre-configured contents we got from pre-rendering // Write the index.html file with the pre-configured contents we got from pre-rendering
std::fs::write( std::fs::write(
self.app_dir().join("index.html"), self.build.root_dir().join("index.html"),
self.build.prepare_html()?, self.build.prepare_html()?,
)?; )?;
} }
@ -385,7 +376,7 @@ impl AppBundle {
return Ok(()); return Ok(());
} }
let asset_dir = self.asset_dir(); let asset_dir = self.build.asset_dir();
// First, clear the asset dir // First, clear the asset dir
// todo(jon): cache the asset dir, removing old files and only copying new ones that changed since the last build // todo(jon): cache the asset dir, removing old files and only copying new ones that changed since the last build
@ -418,10 +409,9 @@ impl AppBundle {
// Parallel Copy over the assets and keep track of progress with an atomic counter // Parallel Copy over the assets and keep track of progress with an atomic counter
// todo: we want to use the fastfs variant that knows how to parallelize folders, too // todo: we want to use the fastfs variant that knows how to parallelize folders, too
assets_to_transfer.par_iter().try_for_each(|(from, to)| { assets_to_transfer.par_iter().try_for_each(|(from, to)| {
self.build.status_copying_asset( tracing::trace!(
assets_finished.fetch_add(0, std::sync::atomic::Ordering::SeqCst), "Starting asset copy {current}/{asset_count} from {from:?}",
asset_count, current = assets_finished.fetch_add(0, std::sync::atomic::Ordering::SeqCst),
from.clone(),
); );
// todo(jon): implement optimize + pre_compress on the asset type // todo(jon): implement optimize + pre_compress on the asset type
@ -431,7 +421,7 @@ impl AppBundle {
tracing::error!("Failed to copy asset {from:?}: {err}"); tracing::error!("Failed to copy asset {from:?}: {err}");
} }
self.build.status_copying_asset( self.build.status_copied_asset(
assets_finished.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1, assets_finished.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1,
asset_count, asset_count,
from.clone(), from.clone(),
@ -443,66 +433,11 @@ impl AppBundle {
Ok(()) Ok(())
} }
/// The directory in which we'll put the main exe
///
/// Mac, Android, Web are a little weird
/// - mac wants to be in Contents/MacOS
/// - android wants to be in jniLibs/arm64-v8a (or others, depending on the platform / architecture)
/// - web wants to be in wasm (which... we don't really need to, we could just drop the wasm into public and it would work)
///
/// 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 {
match self.build.build.platform() {
Platform::MacOS => self.app_dir().join("Contents").join("MacOS"),
Platform::Android => self.app_dir().join("jniLibs").join("arm64-v8a"),
Platform::Web => self.app_dir().join("wasm"),
// these are all the same, I think?
Platform::Windows
| Platform::Linux
| Platform::Ios
| Platform::Server
| Platform::Liveview => self.app_dir(),
}
}
/// The item that we'll try to run directly if we need to. /// 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 /// 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 fn main_exe(&self) -> PathBuf {
// todo(jon): this could just be named `App` or the name of the app like `Raycast` in `Raycast.app` self.build.exe_dir().join(self.build.platform_exe_name())
match self.build.build.platform() {
Platform::MacOS => self.exe_dir().join("DioxusApp"),
Platform::Ios => self.exe_dir().join("DioxusApp"),
Platform::Server => self.exe_dir().join("server"),
Platform::Liveview => self.exe_dir().join("server"),
Platform::Windows => self.exe_dir().join("app.exe"),
Platform::Linux => self.exe_dir().join("AppRun"), // from the appimage spec, the root exe needs to be named `AppRun`
Platform::Android => self.exe_dir().join("libdioxusapp.so"), // from the apk spec, the root exe will actually be a shared library
Platform::Web => unimplemented!("there's no main exe on web"), // this will be wrong, I think, but not important?
}
}
pub fn asset_dir(&self) -> PathBuf {
match self.build.build.platform() {
// macos why are you weird
Platform::MacOS => self
.app_dir()
.join("Contents")
.join("Resources")
.join("assets"),
// everyone else is soooo normal, just app/assets :)
Platform::Web
| Platform::Ios
| Platform::Windows
| Platform::Linux
| Platform::Android
| Platform::Server
| Platform::Liveview => self.app_dir().join("assets"),
}
} }
/// We always put the server in the `web` folder! /// We always put the server in the `web` folder!
@ -531,15 +466,15 @@ impl AppBundle {
// write the Info.plist file // write the Info.plist file
match self.build.build.platform() { match self.build.build.platform() {
Platform::MacOS => { Platform::MacOS => {
let src = include_str!("../../assets/macos/mac.plist"); let dest = self.build.root_dir().join("Contents").join("Info.plist");
let dest = self.app_dir().join("Contents").join("Info.plist"); let plist = self.macos_plist_contents()?;
std::fs::write(dest, src)?; std::fs::write(dest, plist)?;
} }
Platform::Ios => { Platform::Ios => {
let src = include_str!("../../assets/ios/ios.plist"); let dest = self.build.root_dir().join("Info.plist");
let dest = self.app_dir().join("Info.plist"); let plist = self.ios_plist_contents()?;
std::fs::write(dest, src)?; std::fs::write(dest, plist)?;
} }
// AndroidManifest.xml // AndroidManifest.xml
@ -575,7 +510,7 @@ impl AppBundle {
.krate .krate
.should_pre_compress_web_assets(self.build.build.release); .should_pre_compress_web_assets(self.build.build.release);
let bindgen_dir = self.exe_dir(); let bindgen_dir = self.build.exe_dir();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
crate::fastfs::pre_compress_folder(&bindgen_dir, pre_compress) crate::fastfs::pre_compress_folder(&bindgen_dir, pre_compress)
}) })
@ -618,37 +553,6 @@ impl AppBundle {
None None
} }
/// returns the path to .app/.apk/.appimage folder
///
/// we only add an extension to the folders where it sorta matters that it's named with the extension.
/// for example, on mac, the `.app` indicates we can `open` it and it pulls in icons, dylibs, etc.
///
/// for our simulator-based platforms, this is less important since they need to be zipped up anyways
/// to run in the simulator.
///
/// For windows/linux, it's also not important since we're just running the exe directly out of the folder
pub(crate) fn app_dir(&self) -> PathBuf {
let platform_dir = self
.build
.krate
.build_dir(self.build.build.platform(), self.build.build.release);
match self.build.build.platform() {
Platform::Web => platform_dir.join("public"),
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("DioxusApp.app"),
Platform::Ios => platform_dir.join("DioxusApp.app"),
// in theory, these all could end up in the build dir
Platform::Linux => platform_dir.join("app"), // .appimage (after bundling)
Platform::Windows => platform_dir.join("app"), // .exe (after bundling)
Platform::Android => platform_dir.join("app"), // .apk (after bundling)
Platform::Liveview => platform_dir.join("app"), // .exe (after bundling)
}
}
pub(crate) async fn run_wasm_bindgen( pub(crate) async fn run_wasm_bindgen(
&self, &self,
input_path: &Path, input_path: &Path,
@ -691,7 +595,6 @@ impl AppBundle {
if !self.build.build.release { if !self.build.build.release {
return Ok(()); return Ok(());
}; };
self.build.status_optimizing_wasm(); self.build.status_optimizing_wasm();
#[cfg(feature = "optimizations")] #[cfg(feature = "optimizations")]
@ -799,4 +702,76 @@ impl AppBundle {
Ok(()) Ok(())
} }
pub(crate) fn bundle_identifier(&self) -> String {
format!("com.dioxuslabs.{}", self.build.krate.executable_name())
}
fn macos_plist_contents(&self) -> Result<String> {
handlebars::Handlebars::new()
.render_template(
include_str!("../../assets/macos/mac.plist.hbs"),
&InfoPlistData {
display_name: self.build.platform_exe_name(),
bundle_name: self.build.platform_exe_name(),
executable_name: self.build.platform_exe_name(),
bundle_identifier: format!("com.dioxuslabs.{}", self.build.platform_exe_name()),
},
)
.map_err(|e| e.into())
}
fn ios_plist_contents(&self) -> Result<String> {
handlebars::Handlebars::new()
.render_template(
include_str!("../../assets/ios/ios.plist.hbs"),
&InfoPlistData {
display_name: self.build.platform_exe_name(),
bundle_name: self.build.platform_exe_name(),
executable_name: self.build.platform_exe_name(),
bundle_identifier: format!("com.dioxuslabs.{}", self.build.platform_exe_name()),
},
)
.map_err(|e| e.into())
}
/// Run any final tools to produce apks or other artifacts we might need.
async fn assemble(&self) -> Result<()> {
if let Platform::Android = self.build.build.platform() {
// make sure we can execute the gradlew script
#[cfg(unix)]
{
use std::os::unix::prelude::PermissionsExt;
std::fs::set_permissions(
self.build.root_dir().join("gradlew"),
std::fs::Permissions::from_mode(0o755),
)?;
}
let output = Command::new("./gradlew")
.arg("assembleDebug")
.current_dir(self.build.root_dir())
.stderr(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.output()
.await?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to assemble apk: {output:?}").into());
}
}
Ok(())
}
pub(crate) fn apk_path(&self) -> PathBuf {
self.build
.root_dir()
.join("app")
.join("build")
.join("outputs")
.join("apk")
.join("debug")
.join("app-debug.apk")
}
} }

View file

@ -5,14 +5,15 @@
//! Uses a request -> response architecture that allows you to monitor the progress with an optional message //! Uses a request -> response architecture that allows you to monitor the progress with an optional message
//! receiver. //! receiver.
mod build; mod builder;
mod bundle; mod bundle;
mod progress; mod progress;
mod runner; mod request;
mod templates;
mod verify; mod verify;
mod web; mod web;
pub(crate) use build::*; pub(crate) use builder::*;
pub(crate) use bundle::*; pub(crate) use bundle::*;
pub(crate) use progress::*; pub(crate) use progress::*;
pub(crate) use runner::*; pub(crate) use request::*;

View file

@ -1,5 +1,5 @@
//! Report progress about the build to the user. We use channels to report progress back to the CLI. //! Report progress about the build to the user. We use channels to report progress back to the CLI.
use crate::{AppBundle, BuildRequest, Platform, TraceSrc}; use crate::{AppBundle, BuildRequest, BuildStage, Platform, TraceSrc};
use cargo_metadata::CompilerMessage; use cargo_metadata::CompilerMessage;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use std::path::PathBuf; use std::path::PathBuf;
@ -16,46 +16,12 @@ pub(crate) enum BuildUpdate {
BuildFailed { err: crate::Error }, BuildFailed { err: crate::Error },
} }
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuildStage {
Initializing,
Starting {
platform: Platform,
crate_count: usize,
},
InstallingTooling {},
Compiling {
platform: Platform,
current: usize,
total: usize,
krate: String,
},
Bundling {},
RunningBindgen {},
OptimizingWasm {},
CopyingAssets {
current: usize,
total: usize,
path: PathBuf,
},
Success,
Failed,
Aborted,
Restarting,
}
impl BuildRequest { impl BuildRequest {
pub(crate) fn status_wasm_bindgen_start(&self) { pub(crate) fn status_wasm_bindgen_start(&self) {
_ = self.progress.unbounded_send(BuildUpdate::Progress { _ = self.progress.unbounded_send(BuildUpdate::Progress {
stage: BuildStage::RunningBindgen {}, stage: BuildStage::RunningBindgen {},
}); });
} }
pub(crate) fn status_wasm_opt_start(&self) {
_ = self.progress.unbounded_send(BuildUpdate::Progress {
stage: BuildStage::RunningBindgen {},
});
}
pub(crate) fn status_start_bundle(&self) { pub(crate) fn status_start_bundle(&self) {
_ = self.progress.unbounded_send(BuildUpdate::Progress { _ = self.progress.unbounded_send(BuildUpdate::Progress {
@ -73,19 +39,13 @@ impl BuildRequest {
tracing::trace!(dx_src = ?TraceSrc::Cargo, "{line}"); tracing::trace!(dx_src = ?TraceSrc::Cargo, "{line}");
} }
pub(crate) fn status_build_progress( pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) {
&self,
count: usize,
total: usize,
name: String,
platform: Platform,
) {
_ = self.progress.unbounded_send(BuildUpdate::Progress { _ = self.progress.unbounded_send(BuildUpdate::Progress {
stage: BuildStage::Compiling { stage: BuildStage::Compiling {
current: count, current: count,
total, total,
krate: name, krate: name,
platform, is_server: self.is_server(),
}, },
}); });
} }
@ -93,14 +53,13 @@ impl BuildRequest {
pub(crate) fn status_starting_build(&self, crate_count: usize) { pub(crate) fn status_starting_build(&self, crate_count: usize) {
_ = self.progress.unbounded_send(BuildUpdate::Progress { _ = self.progress.unbounded_send(BuildUpdate::Progress {
stage: BuildStage::Starting { stage: BuildStage::Starting {
platform: self.build.platform(), is_server: self.build.platform() == Platform::Server,
crate_count, crate_count,
}, },
}); });
} }
pub(crate) fn status_copying_asset(&self, current: usize, total: usize, path: PathBuf) { pub(crate) fn status_copied_asset(&self, current: usize, total: usize, path: PathBuf) {
tracing::trace!("Status copying asset {current}/{total} from {path:?}");
_ = self.progress.unbounded_send(BuildUpdate::Progress { _ = self.progress.unbounded_send(BuildUpdate::Progress {
stage: BuildStage::CopyingAssets { stage: BuildStage::CopyingAssets {
current, current,
@ -121,4 +80,8 @@ impl BuildRequest {
stage: BuildStage::InstallingTooling {}, stage: BuildStage::InstallingTooling {},
}); });
} }
pub(crate) fn is_server(&self) -> bool {
self.build.platform() == Platform::Server
}
} }

View file

@ -2,14 +2,11 @@ use super::{progress::ProgressTx, BuildArtifacts};
use crate::dioxus_crate::DioxusCrate; use crate::dioxus_crate::DioxusCrate;
use crate::Result; use crate::Result;
use crate::{assets::AssetManifest, TraceSrc}; use crate::{assets::AssetManifest, TraceSrc};
use crate::{build::BuildArgs, link::LinkAction}; use crate::{link::LinkAction, BuildArgs};
use crate::{AppBundle, Platform}; use crate::{AppBundle, Platform};
use anyhow::Context; use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{path::PathBuf, process::Stdio, time::Instant};
path::{Path, PathBuf},
process::Stdio,
};
use tokio::{io::AsyncBufReadExt, process::Command}; use tokio::{io::AsyncBufReadExt, process::Command};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -44,17 +41,32 @@ impl BuildRequest {
pub(crate) async fn build_all(self) -> Result<AppBundle> { pub(crate) async fn build_all(self) -> Result<AppBundle> {
tracing::debug!("Running build command..."); tracing::debug!("Running build command...");
let (app, server) = let (app, server) = self.build_concurrent().await?;
futures_util::future::try_join(self.build_app(), self.build_server()).await?;
AppBundle::new(self, app, server).await AppBundle::new(self, app, server).await
} }
/// Run the build command with a pretty loader, returning the executable output location
async fn build_concurrent(&self) -> Result<(BuildArtifacts, Option<BuildArtifacts>)> {
let (app, server) =
futures_util::future::try_join(self.build_app(), self.build_server()).await?;
Ok((app, server))
}
pub(crate) async fn build_app(&self) -> Result<BuildArtifacts> { pub(crate) async fn build_app(&self) -> Result<BuildArtifacts> {
tracing::debug!("Building app..."); tracing::debug!("Building app...");
let start = Instant::now();
self.prepare_build_dir()?;
let exe = self.build_cargo().await?; let exe = self.build_cargo().await?;
let assets = self.collect_assets(&exe).await?; let assets = self.collect_assets().await?;
Ok(BuildArtifacts { exe, assets })
Ok(BuildArtifacts {
exe,
assets,
time_taken: start.elapsed(),
})
} }
pub(crate) async fn build_server(&self) -> Result<Option<BuildArtifacts>> { pub(crate) async fn build_server(&self) -> Result<Option<BuildArtifacts>> {
@ -87,8 +99,9 @@ impl BuildRequest {
.current_dir(self.krate.crate_dir()) .current_dir(self.krate.crate_dir())
.arg("--message-format") .arg("--message-format")
.arg("json-diagnostic-rendered-ansi") .arg("json-diagnostic-rendered-ansi")
.args(self.build_arguments()); .args(self.build_arguments())
// .env("RUSTFLAGS", self.rust_flags()); .envs(self.env_vars());
// .args(["--", "-Csave-temps=y"]);
if let Some(target_dir) = self.custom_target_dir.as_ref() { if let Some(target_dir) = self.custom_target_dir.as_ref() {
cmd.env("CARGO_TARGET_DIR", target_dir); cmd.env("CARGO_TARGET_DIR", target_dir);
@ -153,7 +166,6 @@ impl BuildRequest {
units_compiled, units_compiled,
crate_count, crate_count,
artifact.target.name, artifact.target.name,
self.build.platform(),
), ),
} }
} }
@ -188,19 +200,14 @@ impl BuildRequest {
/// This will execute `dx` with an env var set to force `dx` to operate as a linker, and then /// This will execute `dx` with an env var set to force `dx` to operate as a linker, and then
/// traverse the .o and .rlib files rustc passes that new `dx` instance, collecting the link /// traverse the .o and .rlib files rustc passes that new `dx` instance, collecting the link
/// tables marked by manganis and parsing them as a ResourceAsset. /// tables marked by manganis and parsing them as a ResourceAsset.
pub(crate) async fn collect_assets(&self, exe: &Path) -> Result<AssetManifest> { pub(crate) async fn collect_assets(&self) -> Result<AssetManifest> {
tracing::debug!("Collecting assets ..."); tracing::debug!("Collecting assets ...");
// If assets are skipped, we don't need to collect them
if self.build.skip_assets { if self.build.skip_assets {
return Ok(AssetManifest::default()); return Ok(AssetManifest::default());
} }
let mut manifest = AssetManifest::default(); self.deep_linker_asset_extract().await
_ = manifest.add_from_object_path(exe.to_path_buf());
Ok(manifest)
} }
/// Create a list of arguments for cargo builds /// Create a list of arguments for cargo builds
@ -254,11 +261,8 @@ impl BuildRequest {
} }
} }
if self.build.verbose { // We always run in verbose since the CLI itself is the one doing the presentation
cargo_args.push("--verbose".to_string()); cargo_args.push("--verbose".to_string());
} else {
cargo_args.push("--quiet".to_string());
}
if self.build.target_args.no_default_features { if self.build.target_args.no_default_features {
cargo_args.push("--no-default-features".to_string()); cargo_args.push("--no-default-features".to_string());
@ -293,7 +297,7 @@ impl BuildRequest {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn rust_flags(&self) -> String { pub(crate) fn android_rust_flags(&self) -> String {
let mut rust_flags = std::env::var("RUSTFLAGS").unwrap_or_default(); let mut rust_flags = std::env::var("RUSTFLAGS").unwrap_or_default();
if self.build.platform() == Platform::Android { if self.build.platform() == Platform::Android {
@ -355,6 +359,7 @@ impl BuildRequest {
.arg("-Z") .arg("-Z")
.arg("unstable-options") .arg("unstable-options")
.args(self.build_arguments()) .args(self.build_arguments())
.envs(self.env_vars())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.output() .output()
@ -394,7 +399,6 @@ impl BuildRequest {
/// ///
/// There's a chance that's not actually true, so this function is kept around in case we do /// 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". /// need to revert to "deep extraction".
#[allow(unused)]
async fn deep_linker_asset_extract(&self) -> Result<AssetManifest> { async fn deep_linker_asset_extract(&self) -> Result<AssetManifest> {
// Create a temp file to put the output of the args // 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 // We need to do this since rustc won't actually print the link args to stdout, so we need to
@ -406,9 +410,9 @@ impl BuildRequest {
// //
// This will force `dx` to look through the incremental cache and find the assets from the previous build // This will force `dx` to look through the incremental cache and find the assets from the previous build
Command::new("cargo") Command::new("cargo")
// .env("RUSTFLAGS", self.rust_flags())
.arg("rustc") .arg("rustc")
.args(self.build_arguments()) .args(self.build_arguments())
.envs(self.env_vars())
.arg("--offline") /* don't use the network, should already be resolved */ .arg("--offline") /* don't use the network, should already be resolved */
.arg("--") .arg("--")
.arg(format!( .arg(format!(
@ -422,7 +426,7 @@ impl BuildRequest {
.env( .env(
LinkAction::ENV_VAR_NAME, LinkAction::ENV_VAR_NAME,
LinkAction::BuildAssetManifest { LinkAction::BuildAssetManifest {
destination: tmp_file.path().to_path_buf(), destination: tmp_file.path().to_path_buf().clone(),
} }
.to_json(), .to_json(),
) )
@ -432,6 +436,351 @@ impl BuildRequest {
.await?; .await?;
// The linker wrote the manifest to the temp file, let's load it! // The linker wrote the manifest to the temp file, let's load it!
Ok(AssetManifest::load_from_file(tmp_file.path())?) let manifest = AssetManifest::load_from_file(tmp_file.path())?;
if let Ok(path) = std::env::var("DEEPLINK").map(|s| s.parse::<PathBuf>().unwrap()) {
_ = tmp_file.persist(path);
}
Ok(manifest)
}
fn env_vars(&self) -> Vec<(&str, String)> {
let mut env_vars = vec![];
if self.build.platform() == Platform::Android {
let app = self.root_dir().join("app");
let app_main = app.join("src").join("main");
let app_kotlin = app_main.join("kotlin");
let app_kotlin_out = app_kotlin.join("com").join("example").join("androidfinal");
env_vars.push((
"WRY_ANDROID_PACKAGE",
"com.example.androidfinal".to_string(),
));
env_vars.push(("WRY_ANDROID_LIBRARY", "androidfinal".to_string()));
env_vars.push((
"WRY_ANDROID_KOTLIN_FILES_OUT_DIR",
app_kotlin_out.display().to_string(),
));
env_vars.push(("RUSTFLAGS", self.android_rust_flags()))
};
env_vars
}
/// We only really currently care about:
///
/// - app dir (.app, .exe, .apk, etc)
/// - assets dir
/// - exe dir (.exe, .app, .apk, etc)
/// - extra scaffolding
///
/// 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<Result<()>> = OnceCell::new();
let success = INITIALIZED.get_or_init(|| {
_ = remove_dir_all(self.root_dir());
create_dir_all(self.root_dir())?;
create_dir_all(self.exe_dir())?;
create_dir_all(self.asset_dir())?;
tracing::debug!("Initialized Root dir: {:?}", self.root_dir());
tracing::debug!("Initialized Exe dir: {:?}", self.exe_dir());
tracing::debug!("Initialized Asset dir: {:?}", self.asset_dir());
// we could download the templates from somewhere (github?) but after having banged my head against
// cargo-mobile2 for ages, I give up with that. We're literally just going to hardcode the templates
// by writing them here.
if let Platform::Android = self.build.platform() {
self.build_android_app_dir()?;
}
Ok(())
});
if let Err(e) = success.as_ref() {
return Err(format!("Failed to initialize build directory: {e}").into());
}
Ok(())
}
/// The directory in which we'll put the main exe
///
/// Mac, Android, Web are a little weird
/// - mac wants to be in Contents/MacOS
/// - android wants to be in jniLibs/arm64-v8a (or others, depending on the platform / architecture)
/// - web wants to be in wasm (which... we don't really need to, we could just drop the wasm into public and it would work)
///
/// 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 {
match self.build.platform() {
Platform::MacOS => self.root_dir().join("Contents").join("MacOS"),
Platform::Web => self.root_dir().join("wasm"),
// Android has a whole build structure to it
Platform::Android => self
.root_dir()
.join("app")
.join("src")
.join("main")
.join("jniLibs")
.join("arm64-v8a"),
// these are all the same, I think?
Platform::Windows
| Platform::Linux
| Platform::Ios
| Platform::Server
| Platform::Liveview => self.root_dir(),
}
}
/// returns the path to root build folder. This will be our working directory for the build.
///
/// we only add an extension to the folders where it sorta matters that it's named with the extension.
/// for example, on mac, the `.app` indicates we can `open` it and it pulls in icons, dylibs, etc.
///
/// for our simulator-based platforms, this is less important since they need to be zipped up anyways
/// to run in the simulator.
///
/// For windows/linux, it's also not important since we're just running the exe directly out of the folder
///
/// The idea of this folder is that we can run our top-level build command against it and we'll get
/// a final build output somewhere. Some platforms have basically no build command, and can simply
/// be ran by executing the exe directly.
pub(crate) fn root_dir(&self) -> PathBuf {
let platform_dir = self
.krate
.build_dir(self.build.platform(), self.build.release);
match self.build.platform() {
Platform::Web => platform_dir.join("public"),
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.platform_exe_name())),
Platform::Ios => platform_dir.join(format!("{}.app", self.platform_exe_name())),
// in theory, these all could end up directly in the root dir
Platform::Android => platform_dir.join("app"), // .apk (after bundling)
Platform::Linux => platform_dir.join("app"), // .appimage (after bundling)
Platform::Windows => platform_dir.join("app"), // .exe (after bundling)
Platform::Liveview => platform_dir.join("app"), // .exe (after bundling)
}
}
pub fn asset_dir(&self) -> PathBuf {
match self.build.platform() {
Platform::MacOS => self
.root_dir()
.join("Contents")
.join("Resources")
.join("assets"),
Platform::Android => self
.root_dir()
.join("app")
.join("src")
.join("main")
.join("assets"),
// everyone else is soooo normal, just app/assets :)
Platform::Web
| Platform::Ios
| Platform::Windows
| Platform::Linux
| Platform::Server
| Platform::Liveview => self.root_dir().join("assets"),
}
}
pub fn platform_exe_name(&self) -> String {
match self.build.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()),
// from the apk spec, the root exe will actually be a shared library
Platform::Android => format!("lib{}.so", self.krate.executable_name()),
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(),
}
}
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)?;
tracing::debug!("Initialized Gradle wrapper: {:?}", wrapper);
// app
let app = root.join("app");
let app_main = app.join("src").join("main");
let app_kotlin = app_main.join("kotlin");
let app_jnilibs = app_main.join("jniLibs");
let app_assets = app_main.join("assets");
let app_kotlin_out = app_kotlin.join("com").join("example").join("androidfinal");
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)?;
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);
// Top-level gradle config
write(
root.join("build.gradle.kts"),
include_bytes!("../../assets/android/gen/build.gradle.kts"),
)?;
write(
root.join("gradle.properties"),
include_bytes!("../../assets/android/gen/gradle.properties"),
)?;
write(
root.join("gradlew"),
include_bytes!("../../assets/android/gen/gradlew"),
)?;
write(
root.join("gradlew.bat"),
include_bytes!("../../assets/android/gen/gradlew.bat"),
)?;
write(
root.join("settings.gradle"),
include_bytes!("../../assets/android/gen/settings.gradle"),
)?;
// Then the wrapper and its properties
write(
wrapper.join("gradle-wrapper.properties"),
include_bytes!("../../assets/android/gen/gradle/wrapper/gradle-wrapper.properties"),
)?;
write(
wrapper.join("gradle-wrapper.jar"),
include_bytes!("../../assets/android/gen/gradle/wrapper/gradle-wrapper.jar"),
)?;
// Now the app directory
write(
app.join("build.gradle.kts"),
include_bytes!("../../assets/android/gen/app/build.gradle.kts"),
)?;
write(
app.join("proguard-rules.pro"),
include_bytes!("../../assets/android/gen/app/proguard-rules.pro"),
)?;
write(
app.join("src").join("main").join("AndroidManifest.xml"),
include_bytes!("../../assets/android/gen/app/src/main/AndroidManifest.xml"),
)?;
// Write the main activity manually since tao dropped support for it
write(
app_main
.join("kotlin")
.join("com")
.join("example")
.join("androidfinal")
.join("MainActivity.kt"),
include_bytes!("../../assets/android/MainActivity.kt"),
)?;
// Write the res folder
let res = app_main.join("res");
create_dir_all(&res)?;
create_dir_all(res.join("values"))?;
write(
res.join("values").join("strings.xml"),
include_bytes!("../../assets/android/gen/app/src/main/res/values/strings.xml"),
)?;
write(
res.join("values").join("colors.xml"),
include_bytes!("../../assets/android/gen/app/src/main/res/values/colors.xml"),
)?;
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(
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(
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(
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(
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(
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(
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(
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(
res.join("mipmap-xxxhdpi").join("ic_launcher.webp"),
include_bytes!(
"../../assets/android/gen/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp"
),
)?;
Ok(())
} }
} }

View file

@ -0,0 +1,7 @@
#[derive(serde::Serialize)]
pub struct InfoPlistData {
pub display_name: String,
pub bundle_name: String,
pub bundle_identifier: String,
pub executable_name: String,
}

View file

@ -94,21 +94,21 @@ impl BuildRequest {
/// should be installing the x86 versions. /// should be installing the x86 versions.
pub(crate) async fn verify_ios_tooling(&self, _rustup: RustupShow) -> Result<()> { pub(crate) async fn verify_ios_tooling(&self, _rustup: RustupShow) -> Result<()> {
// open the simulator // open the simulator
_ = tokio::process::Command::new("open") // _ = tokio::process::Command::new("open")
.arg("/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app") // .arg("/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app")
.stderr(Stdio::piped()) // .stderr(Stdio::piped())
.stdout(Stdio::piped()) // .stdout(Stdio::piped())
.status() // .status()
.await; // .await;
// Now xcrun to open the device // Now xcrun to open the device
// todo: we should try and query the device list and/or parse it rather than hardcode this simulator // todo: we should try and query the device list and/or parse it rather than hardcode this simulator
_ = tokio::process::Command::new("xcrun") // _ = tokio::process::Command::new("xcrun")
.args(["simctl", "boot", "83AE3067-987F-4F85-AE3D-7079EF48C967"]) // .args(["simctl", "boot", "83AE3067-987F-4F85-AE3D-7079EF48C967"])
.stderr(Stdio::piped()) // .stderr(Stdio::piped())
.stdout(Stdio::piped()) // .stdout(Stdio::piped())
.status() // .status()
.await; // .await;
// if !rustup // if !rustup
// .installed_toolchains // .installed_toolchains

View file

@ -3,12 +3,6 @@ use crate::{
NSISInstallerMode, NsisSettings, PackageType, WebviewInstallMode, WindowsSettings, WixSettings, NSISInstallerMode, NsisSettings, PackageType, WebviewInstallMode, WindowsSettings, WixSettings,
}; };
pub(crate) fn make_tauri_bundler_settings(
bundle_config: BundleConfig,
) -> tauri_bundler::BundleSettings {
bundle_config.into()
}
impl From<NsisSettings> for tauri_bundler::NsisSettings { impl From<NsisSettings> for tauri_bundler::NsisSettings {
fn from(val: NsisSettings) -> Self { fn from(val: NsisSettings) -> Self {
tauri_bundler::NsisSettings { tauri_bundler::NsisSettings {
@ -162,6 +156,7 @@ impl From<PackageType> for tauri_bundler::PackageType {
PackageType::AppImage => Self::AppImage, PackageType::AppImage => Self::AppImage,
PackageType::Dmg => Self::Dmg, PackageType::Dmg => Self::Dmg,
PackageType::Updater => Self::Updater, PackageType::Updater => Self::Updater,
PackageType::Nsis => Self::Nsis,
} }
} }
} }

View file

@ -1,8 +1,9 @@
use super::*; use super::*;
use crate::DioxusCrate; use crate::DioxusCrate;
use anyhow::Context;
use dioxus_autofmt::{IndentOptions, IndentType}; use dioxus_autofmt::{IndentOptions, IndentType};
use rayon::prelude::*; use rayon::prelude::*;
use std::{borrow::Cow, fs, path::Path, process::exit}; use std::{borrow::Cow, fs, path::Path};
// For reference, the rustfmt main.rs file // For reference, the rustfmt main.rs file
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs // https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
@ -37,7 +38,7 @@ pub(crate) struct Autoformat {
} }
impl Autoformat { impl Autoformat {
pub(crate) fn autoformat(self) -> Result<()> { pub(crate) fn autoformat(self) -> Result<StructuredOutput> {
let Autoformat { let Autoformat {
check, check,
raw, raw,
@ -56,9 +57,7 @@ impl Autoformat {
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) { if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
println!("{}", inner); println!("{}", inner);
} else { } else {
// exit process with error return Err("error formatting codeblock".into());
eprintln!("error formatting codeblock");
exit(1);
} }
} else { } else {
// Default to formatting the project. // Default to formatting the project.
@ -68,13 +67,9 @@ impl Autoformat {
package: Some(package), package: Some(package),
..Default::default() ..Default::default()
}; };
let dx_crate = match DioxusCrate::new(&target_args) { let dx_crate =
Ok(x) => x, DioxusCrate::new(&target_args).context("failed to parse crate graph")?;
Err(error) => {
eprintln!("failed to parse crate graph: {error}");
exit(1);
}
};
Cow::Owned(dx_crate.crate_dir()) Cow::Owned(dx_crate.crate_dir())
} else { } else {
Cow::Borrowed(Path::new(".")) Cow::Borrowed(Path::new("."))
@ -83,12 +78,11 @@ impl Autoformat {
if let Err(e) = if let Err(e) =
autoformat_project(check, split_line_attributes, format_rust_code, crate_dir) autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
{ {
eprintln!("error formatting project: {}", e); return Err(format!("error formatting project: {}", e).into());
exit(1);
} }
} }
Ok(()) Ok(StructuredOutput::Success)
} }
} }
@ -106,8 +100,7 @@ fn refactor_file(
fs::read_to_string(&file) fs::read_to_string(&file)
}; };
let Ok(mut s) = file_content else { let Ok(mut s) = file_content else {
eprintln!("failed to open file: {}", file_content.unwrap_err()); return Err(format!("failed to open file: {}", file_content.unwrap_err()).into());
exit(1);
}; };
if format_rust_code { if format_rust_code {
@ -117,8 +110,7 @@ fn refactor_file(
let Ok(Ok(edits)) = let Ok(Ok(edits)) =
syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent)) syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent))
else { else {
eprintln!("failed to format file: {}", s); return Err(format!("failed to format file: {}", s).into());
exit(1);
}; };
let out = dioxus_autofmt::apply_formats(&s, edits); let out = dioxus_autofmt::apply_formats(&s, edits);
@ -126,7 +118,7 @@ fn refactor_file(
if file == "-" { if file == "-" {
print!("{}", out); print!("{}", out);
} else if let Err(e) = fs::write(&file, out) { } else if let Err(e) = fs::write(&file, out) {
eprintln!("failed to write formatted content to file: {e}",); tracing::error!("failed to write formatted content to file: {e}",);
} else { } else {
println!("formatted {}", file); println!("formatted {}", file);
} }
@ -212,7 +204,7 @@ fn autoformat_project(
match res { match res {
Ok(cnt) => Some(cnt), Ok(cnt) => Some(cnt),
Err(err) => { Err(err) => {
eprintln!("error formatting file : {}\n{:#?}", path.display(), err); tracing::error!("error formatting file : {}\n{:#?}", path.display(), err);
None None
} }
} }
@ -222,8 +214,7 @@ fn autoformat_project(
let files_formatted: usize = counts.into_iter().flatten().sum(); let files_formatted: usize = counts.into_iter().flatten().sum();
if files_formatted > 0 && check { if files_formatted > 0 && check {
eprintln!("{} files needed formatting", files_formatted); return Err(format!("{} files needed formatting", files_formatted).into());
exit(1);
} }
Ok(()) Ok(())

View file

@ -1,11 +1,10 @@
use super::*; use super::*;
use crate::{AppBundle, Builder, DioxusCrate, Platform, PROFILE_SERVER}; use crate::{Builder, DioxusCrate, Platform, PROFILE_SERVER};
/// Build the Rust Dioxus app and all of its assets. /// Build the Rust Dioxus app and all of its assets.
/// ///
/// Produces a final output bundle designed to be run on the target platform. /// Produces a final output bundle designed to be run on the target platform.
#[derive(Clone, Debug, Default, Deserialize, Parser)] #[derive(Clone, Debug, Default, Deserialize, Parser)]
#[clap(name = "build")]
pub(crate) struct BuildArgs { pub(crate) struct BuildArgs {
/// Build in release mode [default: false] /// Build in release mode [default: false]
#[clap(long, short)] #[clap(long, short)]
@ -17,21 +16,6 @@ pub(crate) struct BuildArgs {
#[serde(default)] #[serde(default)]
pub(crate) force_sequential: bool, pub(crate) force_sequential: bool,
/// Use verbose output [default: false]
#[clap(long)]
#[serde(default)]
pub(crate) verbose: bool,
/// Use trace output [default: false]
#[clap(long)]
#[serde(default)]
pub(crate) trace: bool,
/// Pass -Awarnings to the cargo build
#[clap(long)]
#[serde(default)]
pub(crate) silent: bool,
/// Build the app with custom a profile /// Build the app with custom a profile
#[clap(long)] #[clap(long)]
pub(crate) profile: Option<String>, pub(crate) profile: Option<String>,
@ -73,12 +57,9 @@ pub(crate) struct BuildArgs {
} }
impl BuildArgs { impl BuildArgs {
pub async fn build_it(&mut self) -> Result<()> { pub async fn run_cmd(mut self) -> Result<StructuredOutput> {
self.build().await?; tracing::info!("Building project...");
Ok(())
}
pub(crate) async fn build(&mut self) -> Result<AppBundle> {
let krate = let krate =
DioxusCrate::new(&self.target_args).context("Failed to load Dioxus workspace")?; DioxusCrate::new(&self.target_args).context("Failed to load Dioxus workspace")?;
@ -86,12 +67,11 @@ impl BuildArgs {
let bundle = Builder::start(&krate, self.clone())?.finish().await?; let bundle = Builder::start(&krate, self.clone())?.finish().await?;
println!( tracing::info!(path = ?bundle.build.root_dir(), "Build completed successfully! 🚀");
"Successfully built! 💫\nBundle at {}",
bundle.app_dir().display()
);
Ok(bundle) Ok(StructuredOutput::BuildFinished {
path: bundle.build.root_dir(),
})
} }
/// Update the arguments of the CLI by inspecting the DioxusCrate itself and learning about how /// Update the arguments of the CLI by inspecting the DioxusCrate itself and learning about how
@ -127,13 +107,13 @@ impl BuildArgs {
// Add any features required to turn on the client // Add any features required to turn on the client
self.target_args self.target_args
.client_features .client_features
.extend(krate.feature_for_platform(platform)); .push(krate.feature_for_platform(platform));
// Add any features required to turn on the server // Add any features required to turn on the server
// This won't take effect in the server is not built, so it's fine to just set it here even if it's not used // This won't take effect in the server is not built, so it's fine to just set it here even if it's not used
self.target_args self.target_args
.server_features .server_features
.extend(krate.feature_for_platform(Platform::Server)); .push(krate.feature_for_platform(Platform::Server));
// Make sure we set the fullstack platform so we actually build the fullstack variant // Make sure we set the fullstack platform so we actually build the fullstack variant
// Users need to enable "fullstack" in their default feature set. // Users need to enable "fullstack" in their default feature set.
@ -150,7 +130,7 @@ impl BuildArgs {
// Set the profile of the build if it's not already set // Set the profile of the build if it's not already set
// We do this for android/wasm since they require // We do this for android/wasm since they require
if self.profile.is_none() { if self.profile.is_none() && !self.release {
match self.platform { match self.platform {
Some(Platform::Android) => { Some(Platform::Android) => {
self.profile = Some(crate::dioxus_crate::PROFILE_ANDROID.to_string()); self.profile = Some(crate::dioxus_crate::PROFILE_ANDROID.to_string());

View file

@ -1,20 +1,36 @@
use crate::bundle_utils::make_tauri_bundler_settings; use crate::{AppBundle, BuildArgs, Builder, DioxusCrate, Platform};
use crate::DioxusCrate;
use crate::{build::BuildArgs, PackageType};
use anyhow::Context; use anyhow::Context;
use std::env::current_dir; use std::collections::HashMap;
use std::str::FromStr; use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
use tauri_bundler::{PackageSettings, SettingsBuilder};
use super::*; use super::*;
/// Bundle the Rust desktop app and all of its assets /// Bundle the Rust desktop app and all of its assets
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
#[clap(name = "bundle")]
pub struct Bundle { pub struct Bundle {
/// The package types to bundle /// The package types to bundle
///
/// Any of:
/// - macos: The macOS application bundle (.app).
/// - ios: The iOS app bundle.
/// - msi: The Windows bundle (.msi).
/// - nsis: The NSIS bundle (.exe).
/// - deb: The Linux Debian package bundle (.deb).
/// - rpm: The Linux RPM bundle (.rpm).
/// - appimage: The Linux AppImage bundle (.AppImage).
/// - dmg: The macOS DMG bundle (.dmg).
/// - updater: The Updater bundle.
#[clap(long)] #[clap(long)]
pub packages: Option<Vec<PackageType>>, pub package_types: Option<Vec<crate::PackageType>>,
/// The directory in which the final bundle will be placed.
///
/// Relative paths will be placed relative to the current working directory.
///
/// We will flatten the artifacts into this directory - there will be no differentiation between
/// artifacts produced by different platforms.
#[clap(long)]
pub outdir: Option<PathBuf>,
/// The arguments for the dioxus build /// The arguments for the dioxus build
#[clap(flatten)] #[clap(flatten)]
@ -22,31 +38,124 @@ pub struct Bundle {
} }
impl Bundle { impl Bundle {
pub(crate) async fn bundle(mut self) -> anyhow::Result<()> { pub(crate) async fn bundle(mut self) -> Result<StructuredOutput> {
tracing::info!("Bundling project...");
let krate = DioxusCrate::new(&self.build_arguments.target_args) let krate = DioxusCrate::new(&self.build_arguments.target_args)
.context("Failed to load Dioxus workspace")?; .context("Failed to load Dioxus workspace")?;
// We always use `release` mode for bundling
self.build_arguments.release = true;
self.build_arguments.resolve(&krate)?; self.build_arguments.resolve(&krate)?;
// Build the app tracing::info!("Building app...");
let bundle = self.build_arguments.build().await?;
let bundle = Builder::start(&krate, self.build_arguments.clone())?
.finish()
.await?;
tracing::info!("Copying app to output directory...");
// If we're building for iOS, we need to bundle the iOS bundle
if self.build_arguments.platform() == Platform::Ios && self.package_types.is_none() {
self.package_types = Some(vec![crate::PackageType::IosBundle]);
}
let mut cmd_result = StructuredOutput::Success;
match self.build_arguments.platform() {
// By default, mac/win/linux work with tauri bundle
Platform::MacOS | Platform::Linux | Platform::Windows => {
let bundles = self.bundle_desktop(krate, bundle)?;
tracing::info!("Bundled app successfully!");
tracing::info!("App produced {} outputs:", bundles.len());
tracing::debug!("Bundling produced bundles: {:#?}", bundles);
// Copy the bundles to the output directory and log their locations
let mut bundle_paths = vec![];
for bundle in bundles {
for src in bundle.bundle_paths {
let src = if let Some(outdir) = &self.outdir {
let dest = outdir.join(src.file_name().unwrap());
crate::fastfs::copy_asset(&src, &dest)?;
dest
} else {
src.clone()
};
tracing::info!(
"{} - [{}]",
bundle.package_type.short_name(),
src.display()
);
bundle_paths.push(src);
}
}
cmd_result = StructuredOutput::BundleOutput {
bundles: bundle_paths,
};
}
Platform::Web => {
tracing::info!("App available at: {}", bundle.build.root_dir().display());
}
Platform::Ios => {
tracing::warn!("Signed iOS bundles are not yet supported");
tracing::info!(
"The bundle is available at: {}",
bundle.build.root_dir().display()
);
}
Platform::Server => {
tracing::info!("Server available at: {}", bundle.build.root_dir().display())
}
Platform::Liveview => tracing::info!(
"Liveview server available at: {}",
bundle.build.root_dir().display()
),
Platform::Android => {
return Err(Error::UnsupportedFeature(
"Android bundles are not yet supported".into(),
));
}
};
Ok(cmd_result)
}
fn bundle_desktop(
&self,
krate: DioxusCrate,
bundle: AppBundle,
) -> Result<Vec<tauri_bundler::Bundle>, Error> {
_ = std::fs::remove_dir_all(krate.bundle_dir(self.build_arguments.platform()));
// copy the binary to the out dir
let package = krate.package(); let package = krate.package();
let mut name: PathBuf = krate.executable_name().into(); let mut name: PathBuf = krate.executable_name().into();
if cfg!(windows) { if cfg!(windows) {
name.set_extension("exe"); name.set_extension("exe");
} }
std::fs::create_dir_all(krate.bundle_dir(self.build_arguments.platform()))?;
std::fs::copy(
&bundle.app.exe,
krate
.bundle_dir(self.build_arguments.platform())
.join(krate.executable_name()),
)?;
// bundle the app
let binaries = vec![ let binaries = vec![
tauri_bundler::BundleBinary::new(name.display().to_string(), true) // We use the name of the exe but it has to be in the same directory
.set_src_path(Some(krate.workspace_dir().display().to_string())), BundleBinary::new(name.display().to_string(), true)
.set_src_path(Some(bundle.app.exe.display().to_string())),
]; ];
let bundle_config = krate.config.bundle.clone(); let mut bundle_settings: BundleSettings = krate.config.bundle.clone().into();
let mut bundle_settings = make_tauri_bundler_settings(bundle_config);
if cfg!(windows) { if cfg!(windows) {
let windows_icon_override = krate.config.bundle.windows.as_ref().map(|w| &w.icon_path); let windows_icon_override = krate.config.bundle.windows.as_ref().map(|w| &w.icon_path);
@ -62,38 +171,28 @@ impl Bundle {
} }
} }
// Don't copy the executable or the old bundle directory if bundle_settings.resources_map.is_none() {
let ignored_files = [krate bundle_settings.resources_map = Some(HashMap::new());
.bundle_dir(self.build_arguments.platform()) }
.join("bundle")];
for entry in std::fs::read_dir(bundle.build.asset_dir())?.flatten() {
for entry in std::fs::read_dir(bundle.asset_dir())?.flatten() { let old = entry.path().canonicalize()?;
let path = entry.path().canonicalize()?; let new = PathBuf::from("assets").join(old.file_name().unwrap());
if ignored_files.iter().any(|f| path.starts_with(f)) { tracing::debug!("Bundled asset: {old:?} -> {new:?}");
continue;
} bundle_settings
.resources_map
// Tauri bundle will add a __root__ prefix if the input path is absolute even though the output path is relative? .as_mut()
// We strip the prefix here to make sure the input path is relative so that the bundler puts the output path in the right place .expect("to be set")
let path = path .insert(old.display().to_string(), new.display().to_string());
.strip_prefix(&current_dir()?)
.unwrap()
.display()
.to_string();
if let Some(resources) = &mut bundle_settings.resources_map {
resources.insert(path, "".to_string());
} else {
bundle_settings.resources_map = Some([(path, "".to_string())].into());
}
} }
// Drain any resources set in the config into the resources map. Tauri bundle doesn't let you set both resources and resources_map https://github.com/DioxusLabs/dioxus/issues/2941
for resource_path in bundle_settings.resources.take().into_iter().flatten() { for resource_path in bundle_settings.resources.take().into_iter().flatten() {
if let Some(resources) = &mut bundle_settings.resources_map { bundle_settings
resources.insert(resource_path, "".to_string()); .resources_map
} else { .as_mut()
bundle_settings.resources_map = Some([(resource_path, "".to_string())].into()); .expect("to be set")
} .insert(resource_path, "".to_string());
} }
let mut settings = SettingsBuilder::new() let mut settings = SettingsBuilder::new()
@ -106,9 +205,11 @@ impl Bundle {
authors: Some(package.authors.clone()), authors: Some(package.authors.clone()),
default_run: Some(krate.executable_name().to_string()), default_run: Some(krate.executable_name().to_string()),
}) })
.log_level(log::Level::Debug)
.binaries(binaries) .binaries(binaries)
.bundle_settings(bundle_settings); .bundle_settings(bundle_settings);
if let Some(packages) = &self.packages {
if let Some(packages) = &self.package_types {
settings = settings.package_types(packages.iter().map(|p| (*p).into()).collect()); settings = settings.package_types(packages.iter().map(|p| (*p).into()).collect());
} }
@ -116,37 +217,23 @@ impl Bundle {
settings = settings.target(target.to_string()); settings = settings.target(target.to_string());
} }
let settings = settings.build(); if self.build_arguments.platform() == Platform::Ios {
settings = settings.target("aarch64-apple-ios".to_string());
// on macos we need to set CI=true (https://github.com/tauri-apps/tauri/issues/2567)
#[cfg(target_os = "macos")]
std::env::set_var("CI", "true");
tauri_bundler::bundle::bundle_project(&settings.unwrap()).unwrap_or_else(|err|{
#[cfg(target_os = "macos")]
panic!("Failed to bundle project: {:#?}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
#[cfg(not(target_os = "macos"))]
panic!("Failed to bundle project: {:#?}", err);
});
Ok(())
}
}
impl FromStr for PackageType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"macos" => Ok(PackageType::MacOsBundle),
"ios" => Ok(PackageType::IosBundle),
"msi" => Ok(PackageType::WindowsMsi),
"deb" => Ok(PackageType::Deb),
"rpm" => Ok(PackageType::Rpm),
"appimage" => Ok(PackageType::AppImage),
"dmg" => Ok(PackageType::Dmg),
"updater" => Ok(PackageType::Updater),
_ => Err(format!("{} is not a valid package type", s)),
} }
let settings = settings.build()?;
tracing::debug!("Bundling project with settings: {:#?}", settings);
if cfg!(target_os = "macos") {
std::env::set_var("CI", "true");
}
let bundles = tauri_bundler::bundle::bundle_project(&settings).inspect_err(|err| {
tracing::error!("Failed to bundle project: {:#?}", err);
if cfg!(target_os = "macos") {
tracing::error!("Make sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)");
}
})?;
Ok(bundles)
} }
} }

View file

@ -1,10 +1,13 @@
//! Run linting against the user's codebase.
//!
//! For reference, the rustfmt main.rs file
//! https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
use super::*; use super::*;
use crate::DioxusCrate; use crate::DioxusCrate;
use anyhow::Context;
use futures_util::{stream::FuturesUnordered, StreamExt}; use futures_util::{stream::FuturesUnordered, StreamExt};
use std::{path::Path, process::exit}; use std::path::Path;
// For reference, the rustfmt main.rs file
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
/// Check the Rust files in the project for issues. /// Check the Rust files in the project for issues.
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
@ -20,25 +23,23 @@ pub(crate) struct Check {
impl Check { impl Check {
// Todo: check the entire crate // Todo: check the entire crate
pub(crate) async fn check(self) -> Result<()> { pub(crate) async fn check(self) -> Result<StructuredOutput> {
match self.file { match self.file {
// Default to checking the project // Default to checking the project
None => { None => {
let dioxus_crate = DioxusCrate::new(&self.target_args)?; let dioxus_crate = DioxusCrate::new(&self.target_args)?;
if let Err(e) = check_project_and_report(dioxus_crate).await { check_project_and_report(dioxus_crate)
eprintln!("error checking project: {}", e); .await
exit(1); .context("error checking project")?;
}
} }
Some(file) => { Some(file) => {
if let Err(e) = check_file_and_report(file).await { check_file_and_report(file)
eprintln!("failed to check file: {}", e); .await
exit(1); .context("error checking file")?;
}
} }
} }
Ok(()) Ok(StructuredOutput::Success)
} }
} }
@ -72,7 +73,7 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
.await; .await;
if res.is_err() { if res.is_err() {
eprintln!("error checking file: {}", path.display()); tracing::error!("error checking file: {}", path.display());
} }
res res
@ -92,19 +93,17 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
for report in issue_reports.into_iter() { for report in issue_reports.into_iter() {
if !report.issues.is_empty() { if !report.issues.is_empty() {
println!("{}", report); tracing::info!("{}", report);
} }
} }
match total_issues { match total_issues {
0 => println!("No issues found."), 0 => {
1 => println!("1 issue found."), tracing::info!("No issues found.");
_ => println!("{} issues found.", total_issues), Ok(())
} }
1 => Err("1 issue found.".into()),
match total_issues { _ => Err(format!("{} issues found.", total_issues).into()),
0 => exit(0),
_ => exit(1),
} }
} }
@ -114,7 +113,6 @@ fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
}; };
// load the gitignore // load the gitignore
for entry in folder { for entry in folder {
let Ok(entry) = entry else { let Ok(entry) = entry else {
continue; continue;

View file

@ -1,25 +1,26 @@
use super::*; use super::*;
use crate::Result;
/// Clean build artifacts. /// Clean build artifacts.
/// ///
/// Simlpy runs `cargo clean` /// Simlpy runs `cargo clean`
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
#[clap(name = "clean")]
pub(crate) struct Clean {} pub(crate) struct Clean {}
impl Clean { impl Clean {
/// todo(jon): we should add a config option that just wipes target/dx and target/dioxus-client instead of doing a full clean /// todo(jon): we should add a config option that just wipes target/dx and target/dioxus-client instead of doing a full clean
pub(crate) fn clean(self) -> anyhow::Result<()> { pub(crate) async fn clean(self) -> Result<StructuredOutput> {
let output = Command::new("cargo") let output = tokio::process::Command::new("cargo")
.arg("clean") .arg("clean")
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.output()?; .output()
.await?;
if !output.status.success() { if !output.status.success() {
return Err(anyhow::anyhow!("Cargo clean failed.")); return Err(anyhow::anyhow!("Cargo clean failed.").into());
} }
Ok(()) Ok(StructuredOutput::Success)
} }
} }

View file

@ -4,7 +4,6 @@ use crate::{metadata::crate_root, CliSettings};
/// Dioxus config file controls /// Dioxus config file controls
#[derive(Clone, Debug, Deserialize, Subcommand)] #[derive(Clone, Debug, Deserialize, Subcommand)]
#[clap(name = "config")]
pub(crate) enum Config { pub(crate) enum Config {
/// Init `Dioxus.toml` for project/folder. /// Init `Dioxus.toml` for project/folder.
Init { Init {
@ -76,7 +75,7 @@ impl From<BoolValue> for bool {
} }
impl Config { impl Config {
pub(crate) fn config(self) -> Result<()> { pub(crate) fn config(self) -> Result<StructuredOutput> {
let crate_root = crate_root()?; let crate_root = crate_root()?;
match self { match self {
Config::Init { Config::Init {
@ -89,7 +88,7 @@ impl Config {
tracing::warn!( tracing::warn!(
"config file `Dioxus.toml` already exist, use `--force` to overwrite it." "config file `Dioxus.toml` already exist, use `--force` to overwrite it."
); );
return Ok(()); return Ok(StructuredOutput::Success);
} }
let mut file = File::create(conf_path)?; let mut file = File::create(conf_path)?;
let content = String::from(include_str!("../../assets/dioxus.toml")) let content = String::from(include_str!("../../assets/dioxus.toml"))
@ -99,7 +98,7 @@ impl Config {
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Init config file completed."); tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Init config file completed.");
} }
Config::FormatPrint {} => { Config::FormatPrint {} => {
println!( tracing::info!(
"{:#?}", "{:#?}",
crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default())?.config crate::dioxus_crate::DioxusCrate::new(&TargetArgs::default())?.config
); );
@ -112,7 +111,7 @@ impl Config {
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Create custom html file done."); tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Create custom html file done.");
} }
Config::LogFile {} => { Config::LogFile {} => {
let log_path = crate::tracer::log_path(); let log_path = crate::logging::FileAppendLayer::log_path();
tracing::info!(dx_src = ?TraceSrc::Dev, "Log file is located at {}", log_path.display()); tracing::info!(dx_src = ?TraceSrc::Dev, "Log file is located at {}", log_path.display());
} }
// Handle CLI settings. // Handle CLI settings.
@ -132,6 +131,7 @@ impl Config {
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 CLI setting `{setting}` has been set."); tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 CLI setting `{setting}` has been set.");
} }
} }
Ok(())
Ok(StructuredOutput::Success)
} }
} }

View file

@ -7,7 +7,6 @@ use std::path::Path;
pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template"; pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template";
#[derive(Clone, Debug, Default, Deserialize, Parser)] #[derive(Clone, Debug, Default, Deserialize, Parser)]
#[clap(name = "new")]
pub(crate) struct Create { pub(crate) struct Create {
/// Project name (required when `--yes` is used) /// Project name (required when `--yes` is used)
name: Option<String>, name: Option<String>,
@ -39,7 +38,7 @@ pub(crate) struct Create {
} }
impl Create { impl Create {
pub(crate) fn create(mut self) -> Result<()> { pub(crate) fn create(mut self) -> Result<StructuredOutput> {
let metadata = cargo_metadata::MetadataCommand::new().exec().ok(); let metadata = cargo_metadata::MetadataCommand::new().exec().ok();
// If we're getting pass a `.` name, that's actually a path // If we're getting pass a `.` name, that's actually a path
@ -106,7 +105,9 @@ impl Create {
.expect("ctrlc::set_handler"); .expect("ctrlc::set_handler");
let path = cargo_generate::generate(args)?; let path = cargo_generate::generate(args)?;
post_create(&path, metadata) post_create(&path, metadata)?;
Ok(StructuredOutput::Success)
} }
} }

View file

@ -1,10 +1,11 @@
use crate::{Result, StructuredOutput};
use clap::Parser; use clap::Parser;
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
pub struct Doctor {} pub struct Doctor {}
impl Doctor { impl Doctor {
pub async fn run(self) -> anyhow::Result<()> { pub async fn run(self) -> Result<StructuredOutput> {
Ok(()) Ok(StructuredOutput::Success)
} }
} }

View file

@ -3,28 +3,27 @@ use crate::cli::create::DEFAULT_TEMPLATE;
use cargo_generate::{GenerateArgs, TemplatePath}; use cargo_generate::{GenerateArgs, TemplatePath};
#[derive(Clone, Debug, Default, Deserialize, Parser)] #[derive(Clone, Debug, Default, Deserialize, Parser)]
#[clap(name = "init")]
pub(crate) struct Init { pub(crate) struct Init {
/// Template path /// Template path
#[clap(default_value = DEFAULT_TEMPLATE, short, long)] #[clap(default_value = DEFAULT_TEMPLATE, short, long)]
template: String, template: String,
/// Pass <option>=<value> for the used template (e.g., `foo=bar`) /// Pass <option>=<value> for the used template (e.g., `foo=bar`)
#[clap(short, long)] #[clap(short, long)]
option: Vec<String>, option: Vec<String>,
/// Specify a sub-template within the template repository to be used as the actual template /// Specify a sub-template within the template repository to be used as the actual template
#[clap(long)] #[clap(long)]
subtemplate: Option<String>, subtemplate: Option<String>,
/// Skip user interaction by using the default values for the used template. /// Skip user interaction by using the default values for the used template.
/// Default values can be overridden with `--option` /// Default values can be overridden with `--option`
#[clap(short, long)] #[clap(short, long)]
yes: bool, yes: bool,
// TODO: turn on/off cargo-generate's output (now is invisible)
// #[clap(default_value = "false", short, long)]
// silent: bool,
} }
impl Init { impl Init {
pub(crate) fn init(self) -> Result<()> { pub(crate) fn init(self) -> Result<StructuredOutput> {
let metadata = cargo_metadata::MetadataCommand::new().exec().ok(); let metadata = cargo_metadata::MetadataCommand::new().exec().ok();
// Get directory name. // Get directory name.
@ -57,6 +56,8 @@ impl Init {
..Default::default() ..Default::default()
}; };
let path = cargo_generate::generate(args)?; let path = cargo_generate::generate(args)?;
create::post_create(&path, metadata) create::post_create(&path, metadata)?;
Ok(StructuredOutput::Success)
} }
} }

View file

@ -1,8 +1,7 @@
use crate::assets::AssetManifest;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
use crate::assets::AssetManifest;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum LinkAction { pub enum LinkAction {
BuildAssetManifest { BuildAssetManifest {
@ -15,7 +14,7 @@ pub enum LinkAction {
} }
impl LinkAction { impl LinkAction {
pub(crate) const ENV_VAR_NAME: &'static str = "dx-magic-link-file"; pub(crate) const ENV_VAR_NAME: &'static str = "dx_magic_link_file";
/// Should we write the input arguments to a file (aka act as a linker subprocess)? /// Should we write the input arguments to a file (aka act as a linker subprocess)?
/// ///
@ -39,7 +38,7 @@ impl LinkAction {
/// ///
/// hmmmmmmmm tbh I'd rather just pass the object files back and do the parsing here, but the interface /// 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 /// is nicer to just bounce back the args and let the host do the parsing/canonicalization
pub(crate) fn run(self) -> anyhow::Result<()> { pub(crate) fn run(self) {
match self { match self {
// Literally just run the android linker :) // Literally just run the android linker :)
LinkAction::LinkAndroid { LinkAction::LinkAndroid {
@ -102,7 +101,5 @@ impl LinkAction {
std::fs::write(dest, contents).expect("Failed to write output file"); std::fs::write(dest, contents).expect("Failed to write output file");
} }
} }
Ok(())
} }
} }

View file

@ -12,12 +12,14 @@ pub(crate) mod run;
pub(crate) mod serve; pub(crate) mod serve;
pub(crate) mod target; pub(crate) mod target;
pub(crate) mod translate; pub(crate) mod translate;
pub(crate) mod verbosity;
pub(crate) use build::*; pub(crate) use build::*;
pub(crate) use serve::*; pub(crate) use serve::*;
pub(crate) use target::*; pub(crate) use target::*;
pub(crate) use verbosity::*;
use crate::{error::Result, Error}; use crate::{error::Result, Error, StructuredOutput};
use anyhow::Context; use anyhow::Context;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use html_parser::Dom; use html_parser::Dom;
@ -35,40 +37,42 @@ use std::{
#[derive(Parser)] #[derive(Parser)]
#[clap(name = "dioxus", version = VERSION.as_str())] #[clap(name = "dioxus", version = VERSION.as_str())]
pub(crate) struct Cli { pub(crate) struct Cli {
#[clap(subcommand)] #[command(subcommand)]
pub(crate) action: Commands, pub(crate) action: Commands,
/// Enable verbose logging. #[command(flatten)]
#[clap(short)] pub(crate) verbosity: Verbosity,
pub(crate) v: bool,
/// Specify a binary target.
#[clap(global = true, long)]
pub(crate) bin: Option<String>,
} }
#[derive(Parser)] #[derive(Subcommand)]
pub(crate) enum Commands { pub(crate) enum Commands {
/// Build the Dioxus project and all of its assets. /// Build the Dioxus project and all of its assets.
#[clap(name = "build")]
Build(build::BuildArgs), Build(build::BuildArgs),
/// Translate a source file into Dioxus code. /// Translate a source file into Dioxus code.
#[clap(name = "translate")]
Translate(translate::Translate), Translate(translate::Translate),
/// Build, watch & serve the Dioxus project and all of its assets. /// Build, watch & serve the Dioxus project and all of its assets.
#[clap(name = "serve")]
Serve(serve::ServeArgs), Serve(serve::ServeArgs),
/// Create a new project for Dioxus. /// Create a new project for Dioxus.
#[clap(name = "new")]
New(create::Create), New(create::Create),
/// Init a new project for Dioxus in an existing directory. /// Init a new project for Dioxus in an existing directory.
/// Will attempt to keep your project in a good state. /// Will attempt to keep your project in a good state.
#[clap(name = "init")]
Init(init::Init), Init(init::Init),
/// Clean output artifacts. /// Clean output artifacts.
#[clap(name = "clean")]
Clean(clean::Clean), Clean(clean::Clean),
/// Bundle the Dioxus app into a shippable object. /// Bundle the Dioxus app into a shippable object.
#[clap(name = "bundle")]
Bundle(bundle::Bundle), Bundle(bundle::Bundle),
/// Automatically format RSX. /// Automatically format RSX.
@ -89,6 +93,7 @@ pub(crate) enum Commands {
/// Dioxus config file controls. /// Dioxus config file controls.
#[clap(subcommand)] #[clap(subcommand)]
#[clap(name = "config")]
Config(config::Config), Config(config::Config),
} }
@ -111,7 +116,7 @@ impl Display for Commands {
} }
} }
pub(crate) static VERSION: Lazy<String> = Lazy::new(|| { static VERSION: Lazy<String> = Lazy::new(|| {
format!( format!(
"{} ({})", "{} ({})",
crate::dx_build_info::PKG_VERSION, crate::dx_build_info::PKG_VERSION,

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use crate::{serve::ServeUpdate, BuildArgs, Builder, DioxusCrate}; use crate::{serve::ServeUpdate, BuildArgs, Builder, DioxusCrate, Result};
/// Run the project with the given arguments /// Run the project with the given arguments
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
@ -10,14 +10,14 @@ pub(crate) struct RunArgs {
} }
impl RunArgs { impl RunArgs {
pub(crate) async fn run(mut self) -> anyhow::Result<()> { pub(crate) async fn run(mut self) -> Result<StructuredOutput> {
let krate = DioxusCrate::new(&self.build_args.target_args) let krate = DioxusCrate::new(&self.build_args.target_args)
.context("Failed to load Dioxus workspace")?; .context("Failed to load Dioxus workspace")?;
self.build_args.resolve(&krate)?; self.build_args.resolve(&krate)?;
println!("Building crate krate data: {:#?}", krate); tracing::trace!("Building crate krate data: {:#?}", krate);
println!("Build args: {:#?}", self.build_args); tracing::trace!("Build args: {:#?}", self.build_args);
let bundle = Builder::start(&krate, self.build_args.clone())? let bundle = Builder::start(&krate, self.build_args.clone())?
.finish() .finish()
@ -35,11 +35,15 @@ impl RunArgs {
// They won't generally be emitted // They won't generally be emitted
loop { loop {
match runner.wait().await { match runner.wait().await {
ServeUpdate::StderrReceived { platform, msg } => println!("[{platform}]: {msg}"), ServeUpdate::StderrReceived { platform, msg } => {
ServeUpdate::StdoutReceived { platform, msg } => println!("[{platform}]: {msg}"), tracing::info!("[{platform}]: {msg}")
}
ServeUpdate::StdoutReceived { platform, msg } => {
tracing::info!("[{platform}]: {msg}")
}
ServeUpdate::ProcessExited { platform, status } => { ServeUpdate::ProcessExited { platform, status } => {
runner.kill(platform); runner.kill(platform);
eprintln!("[{platform}]: process exited with status: {status:?}"); tracing::info!("[{platform}]: process exited with status: {status:?}");
break; break;
} }
ServeUpdate::BuildUpdate { .. } => {} ServeUpdate::BuildUpdate { .. } => {}
@ -55,6 +59,6 @@ impl RunArgs {
} }
} }
Ok(()) Ok(StructuredOutput::Success)
} }
} }

View file

@ -1,11 +1,9 @@
use super::*; use super::*;
use crate::{AddressArguments, BuildArgs, DioxusCrate, Platform}; use crate::{AddressArguments, BuildArgs, DioxusCrate, Platform};
use anyhow::Context;
/// Serve the project /// Serve the project
#[derive(Clone, Debug, Default, Parser)] #[derive(Clone, Debug, Default, Parser)]
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))] #[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
#[clap(name = "serve")]
pub(crate) struct ServeArgs { pub(crate) struct ServeArgs {
/// The arguments for the address the server will run on /// The arguments for the address the server will run on
#[clap(flatten)] #[clap(flatten)]
@ -47,10 +45,22 @@ pub(crate) struct ServeArgs {
impl ServeArgs { impl ServeArgs {
/// Start the tui, builder, etc by resolving the arguments and then running the actual top-level serve function /// Start the tui, builder, etc by resolving the arguments and then running the actual top-level serve function
pub(crate) async fn serve(mut self) -> Result<()> { ///
let krate = DioxusCrate::new(&self.build_arguments.target_args) /// Make sure not to do any intermediate logging since our tracing infra has now enabled much
.context("Failed to load Dioxus workspace")?; /// higher log levels
pub(crate) async fn serve(self) -> Result<StructuredOutput> {
crate::serve::serve_all(self).await?;
Ok(StructuredOutput::Success)
}
pub(crate) fn load_krate(&mut self) -> Result<DioxusCrate> {
let krate = DioxusCrate::new(&self.build_arguments.target_args)?;
self.resolve(&krate)?;
Ok(krate)
}
pub(crate) fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> {
// Enable hot reload. // Enable hot reload.
if self.hot_reload.is_none() { if self.hot_reload.is_none() {
self.hot_reload = Some(krate.settings.always_hot_reload.unwrap_or(true)); self.hot_reload = Some(krate.settings.always_hot_reload.unwrap_or(true));
@ -72,9 +82,9 @@ impl ServeArgs {
} }
// Resolve the build arguments // Resolve the build arguments
self.build_arguments.resolve(&krate)?; self.build_arguments.resolve(krate)?;
crate::serve::serve_all(self, krate).await Ok(())
} }
pub(crate) fn should_hotreload(&self) -> bool { pub(crate) fn should_hotreload(&self) -> bool {

View file

@ -1,11 +1,9 @@
use super::*; use super::*;
use crate::{Result, TraceSrc}; use crate::{Result, StructuredOutput};
use dioxus_rsx::{BodyNode, CallBody, TemplateBody}; use dioxus_rsx::{BodyNode, CallBody, TemplateBody};
use std::{io::IsTerminal as _, process::exit};
/// Translate some source file into Dioxus code /// Translate some source file into Dioxus code
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
#[clap(name = "translate")]
pub(crate) struct Translate { pub(crate) struct Translate {
/// Activate debug mode /// Activate debug mode
// short and long flags (-d, --debug) will be deduced from the field's name // short and long flags (-d, --debug) will be deduced from the field's name
@ -26,7 +24,7 @@ pub(crate) struct Translate {
} }
impl Translate { impl Translate {
pub(crate) fn translate(self) -> Result<()> { pub(crate) fn translate(self) -> Result<StructuredOutput> {
// Get the right input for the translation // Get the right input for the translation
let contents = determine_input(self.file, self.raw)?; let contents = determine_input(self.file, self.raw)?;
@ -34,15 +32,18 @@ impl Translate {
let dom = html_parser::Dom::parse(&contents)?; let dom = html_parser::Dom::parse(&contents)?;
// Convert the HTML to RSX // Convert the HTML to RSX
let out = convert_html_to_formatted_rsx(&dom, self.component); let html = convert_html_to_formatted_rsx(&dom, self.component);
// Write the output // Write the output
// todo(jon): we should probably use tracing out a different output format
// right now we're just printing to stdout since some tools rely on that, but likely we don't want that
// instead we should be printing as json (or maybe even a different format) if we're not interactive
match self.output { match self.output {
Some(output) => std::fs::write(output, out)?, Some(output) => std::fs::write(output, &html)?,
None => print!("{}", out), None => print!("{}", html),
} }
Ok(()) Ok(StructuredOutput::HtmlTranslate { html })
} }
} }
@ -103,10 +104,11 @@ fn indent_and_write(raw: &str, idx: usize, out: &mut String) {
} }
fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String> { fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String> {
use std::io::IsTerminal as _;
// Make sure not both are specified // Make sure not both are specified
if file.is_some() && raw.is_some() { if file.is_some() && raw.is_some() {
tracing::error!(dx_src = ?TraceSrc::Dev, "Only one of --file or --raw should be specified."); return Err("Only one of --file or --raw should be specified.".into());
exit(0);
} }
if let Some(raw) = raw { if let Some(raw) = raw {

View file

@ -0,0 +1,16 @@
use clap::Parser;
#[derive(Parser, Clone, Copy, Debug)]
pub struct Verbosity {
/// Use verbose output [default: false]
#[clap(long, global = true)]
pub(crate) verbose: bool,
/// Use trace output [default: false]
#[clap(long, global = true)]
pub(crate) trace: bool,
/// Output logs in JSON format
#[clap(long, global = true)]
pub(crate) json_output: bool,
}

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::{collections::HashMap, str::FromStr};
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub(crate) struct BundleConfig { pub(crate) struct BundleConfig {
@ -181,18 +181,6 @@ impl Default for WebviewInstallMode {
} }
} }
#[derive(Clone, Copy, Debug)]
pub(crate) enum PackageType {
MacOsBundle,
IosBundle,
WindowsMsi,
Deb,
Rpm,
AppImage,
Dmg,
Updater,
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomSignCommandSettings { pub struct CustomSignCommandSettings {
/// The command to run to sign the binary. /// The command to run to sign the binary.
@ -202,3 +190,35 @@ pub struct CustomSignCommandSettings {
/// "%1" will be replaced with the path to the binary to be signed. /// "%1" will be replaced with the path to the binary to be signed.
pub args: Vec<String>, pub args: Vec<String>,
} }
#[derive(Clone, Copy, Debug)]
pub(crate) enum PackageType {
MacOsBundle,
IosBundle,
WindowsMsi,
Nsis,
Deb,
Rpm,
AppImage,
Dmg,
Updater,
}
impl FromStr for PackageType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"macos" => Ok(PackageType::MacOsBundle),
"ios" => Ok(PackageType::IosBundle),
"msi" => Ok(PackageType::WindowsMsi),
"nsis" => Ok(PackageType::Nsis),
"deb" => Ok(PackageType::Deb),
"rpm" => Ok(PackageType::Rpm),
"appimage" => Ok(PackageType::AppImage),
"dmg" => Ok(PackageType::Dmg),
"updater" => Ok(PackageType::Updater),
_ => Err(format!("{} is not a valid package type", s)),
}
}
}

View file

@ -25,14 +25,15 @@ pub(crate) static PROFILE_SERVER: &str = "dioxus-server";
impl DioxusCrate { impl DioxusCrate {
pub(crate) fn new(target: &TargetArgs) -> Result<Self> { pub(crate) fn new(target: &TargetArgs) -> Result<Self> {
let mut cmd = Cmd::new(); tracing::debug!("Loading crate");
cmd.features(target.features.clone()); let cmd = Cmd::new();
let builder = krates::Builder::new();
let krates = krates::Builder::new() let krates = builder
.build(cmd, |_| {}) .build(cmd, |_| {})
.context("Failed to run cargo metadata")?; .context("Failed to run cargo metadata")?;
let package = find_main_package(&krates, target.package.clone())?; let package = find_main_package(&krates, target.package.clone())?;
tracing::debug!("Found package {package:?}");
let dioxus_config = DioxusConfig::load(&krates, package)?.unwrap_or_default(); let dioxus_config = DioxusConfig::load(&krates, package)?.unwrap_or_default();
@ -173,6 +174,7 @@ impl DioxusCrate {
.get_enabled_features(krate.kid)? .get_enabled_features(krate.kid)?
.iter() .iter()
.flat_map(|feature| { .flat_map(|feature| {
tracing::trace!("Autodetecting platform from feature {feature}");
Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string())) Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string()))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -230,8 +232,6 @@ impl DioxusCrate {
return possible_platforms.first().cloned(); return possible_platforms.first().cloned();
} }
tracing::warn!("Could not autodetect platform. Platform must be explicitly specified. Pass `--platform <platform>` or set a default platform using a cargo feature.");
None None
} }
@ -246,13 +246,13 @@ impl DioxusCrate {
} }
/// Get the features required to build for the given platform /// Get the features required to build for the given platform
pub(crate) fn feature_for_platform(&self, platform: Platform) -> Option<String> { pub(crate) fn feature_for_platform(&self, platform: Platform) -> String {
let package = self.package(); let package = self.package();
// Try to find the feature that activates the dioxus feature for the given platform // Try to find the feature that activates the dioxus feature for the given platform
let dioxus_feature = platform.feature_name(); let dioxus_feature = platform.feature_name();
package.features.iter().find_map(|(key, features)| { let res = package.features.iter().find_map(|(key, features)| {
// if the feature is just the name of the platform, we use that // if the feature is just the name of the platform, we use that
if key == dioxus_feature { if key == dioxus_feature {
return Some(key.clone()); return Some(key.clone());
@ -271,7 +271,16 @@ impl DioxusCrate {
} }
} }
} }
None 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
}) })
} }
@ -312,14 +321,13 @@ impl DioxusCrate {
if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_SERVER) { if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_SERVER) {
let mut server = toml_edit::Table::new(); let mut server = toml_edit::Table::new();
server.insert("inherits", Item::Value("dev".into())); server.insert("inherits", Item::Value("dev".into()));
server.insert("opt-level", Item::Value(2.into())); // server.insert("opt-level", Item::Value(2.into()));
entry.insert(Item::Table(server)); entry.insert(Item::Table(server));
} }
if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_ANDROID) { if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_ANDROID) {
let mut android = toml_edit::Table::new(); let mut android = toml_edit::Table::new();
android.insert("inherits", Item::Value("dev".into())); android.insert("inherits", Item::Value("dev".into()));
android.insert("opt-level", Item::Value(2.into()));
entry.insert(Item::Table(android)); entry.insert(Item::Table(android));
} }
} }
@ -561,59 +569,60 @@ impl std::fmt::Debug for DioxusCrate {
// Find the main package in the workspace // Find the main package in the workspace
fn find_main_package(krates: &Krates, package: Option<String>) -> Result<NodeId> { fn find_main_package(krates: &Krates, package: Option<String>) -> Result<NodeId> {
let kid = match package { if let Some(package) = package {
Some(package) => { let mut workspace_members = krates.workspace_members();
let mut workspace_members = krates.workspace_members(); let found = workspace_members.find_map(|node| {
let found = workspace_members.find_map(|node| { if let krates::Node::Krate { id, krate, .. } = node {
if let krates::Node::Krate { id, krate, .. } = node { if krate.name == package {
if krate.name == package { return Some(id);
return Some(id);
}
}
None
});
if found.is_none() {
eprintln!("Could not find package {package} in the workspace. Did you forget to add it to the workspace?");
eprintln!("Packages in the workspace:");
for package in krates.workspace_members() {
if let krates::Node::Krate { krate, .. } = package {
eprintln!("{}", krate.name());
}
} }
} }
None
});
found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))? 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());
}
}
} }
None => {
// Otherwise find the package that is the closest parent of the current directory let kid = found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?;
let current_dir = std::env::current_dir()?;
let current_dir = current_dir.as_path(); return Ok(krates.nid_for_kid(kid).unwrap());
// 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() { // Otherwise find the package that is the closest parent of the current directory
if let krates::Node::Krate { id, krate, .. } = member { let current_dir = std::env::current_dir()?;
let member_path = krate.manifest_path.parent().unwrap(); let current_dir = current_dir.as_path();
if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {
let len = path.components().count(); // Go through each member and find the path that is a parent of the current directory
match closest_parent { let mut closest_parent = None;
Some((_, closest_parent_len)) => { for member in krates.workspace_members() {
if len < closest_parent_len { if let krates::Node::Krate { id, krate, .. } = member {
closest_parent = Some((id, len)); 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();
None => { match closest_parent {
closest_parent = Some((id, len)); Some((_, closest_parent_len)) => {
} if len < closest_parent_len {
closest_parent = Some((id, len));
} }
} }
None => {
closest_parent = Some((id, len));
}
} }
} }
closest_parent
.map(|(id, _)| id)
.context("Failed to find current package")?
} }
}; }
let kid = closest_parent
.map(|(id, _)| id)
.context("Failed to find current package")?;
let package = krates.nid_for_kid(kid).unwrap(); let package = krates.nid_for_kid(kid).unwrap();
Ok(package) Ok(package)

View file

@ -30,6 +30,15 @@ pub(crate) enum Error {
#[error("Failed to establish proxy: {0}")] #[error("Failed to establish proxy: {0}")]
ProxySetup(String), ProxySetup(String),
#[error("Failed to bundle project: {0}")]
BundleFailed(#[from] tauri_bundler::Error),
#[error("Unsupported feature: {0}")]
UnsupportedFeature(String),
#[error("Failed to render template: {0}")]
TemplateParse(#[from] handlebars::RenderError),
#[error(transparent)] #[error(transparent)]
Other(#[from] anyhow::Error), Other(#[from] anyhow::Error),
} }

View file

@ -14,8 +14,9 @@
//! 3. Build CLI layer for routing tracing logs to the TUI. //! 3. Build CLI layer for routing tracing logs to the TUI.
//! 4. Build fmt layer for non-interactive logging with a custom writer that prevents output during interactive mode. //! 4. Build fmt layer for non-interactive logging with a custom writer that prevents output during interactive mode.
use crate::{serve::ServeUpdate, Platform as TargetPlatform}; use crate::{serve::ServeUpdate, Cli, Commands, Platform as TargetPlatform, Verbosity};
use cargo_metadata::{diagnostic::DiagnosticLevel, CompilerMessage}; use cargo_metadata::{diagnostic::DiagnosticLevel, CompilerMessage};
use clap::Parser;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::{ use std::{
@ -23,37 +24,107 @@ use std::{
env, env,
fmt::{Debug, Display, Write as _}, fmt::{Debug, Display, Write as _},
fs, fs,
io::{self, Write},
path::PathBuf, path::PathBuf,
sync::{ sync::Mutex,
atomic::{AtomicBool, Ordering}, time::Instant,
Mutex,
},
}; };
use tracing::{field::Visit, Level, Subscriber}; use tracing::{field::Visit, Level, Subscriber};
use tracing_subscriber::{fmt::format, prelude::*, registry::LookupSpan, EnvFilter, Layer}; use tracing_subscriber::{
fmt::{
format::{self, Writer},
time::FormatTime,
},
prelude::*,
registry::LookupSpan,
EnvFilter, Layer,
};
const LOG_ENV: &str = "DIOXUS_LOG"; const LOG_ENV: &str = "DIOXUS_LOG";
const LOG_FILE_NAME: &str = "dx.log"; const LOG_FILE_NAME: &str = "dx.log";
const DX_SRC_FLAG: &str = "dx_src"; const DX_SRC_FLAG: &str = "dx_src";
pub fn log_path() -> PathBuf {
let tmp_dir = std::env::temp_dir();
tmp_dir.join(LOG_FILE_NAME)
}
static TUI_ENABLED: AtomicBool = AtomicBool::new(false);
static TUI_TX: OnceCell<UnboundedSender<TraceMsg>> = OnceCell::new(); static TUI_TX: OnceCell<UnboundedSender<TraceMsg>> = OnceCell::new();
pub static VERBOSITY: OnceCell<Verbosity> = OnceCell::new();
pub(crate) struct TraceController { pub(crate) struct TraceController {
pub(crate) tui_rx: UnboundedReceiver<TraceMsg>, pub(crate) tui_rx: UnboundedReceiver<TraceMsg>,
} }
impl TraceController { impl TraceController {
/// Initialize the CLI and set up the tracing infrastructure
pub fn initialize() -> Cli {
let args = Cli::parse();
VERBOSITY
.set(args.verbosity)
.expect("verbosity should only be set once");
// When running in interactive mode (of which serve is the only one), we want to do things slightly differently
// This involves no fmt layer or file logging
if matches!(args.action, Commands::Serve(_)) {
Self::initialize_for_serve();
return args;
}
// By default we capture ourselves at a higher tracing level when serving
// This ensures we're tracing ourselves even if we end up tossing the logs
let filter = if env::var(LOG_ENV).is_ok() {
EnvFilter::from_env(LOG_ENV)
} else {
EnvFilter::new(format!(
"error,dx={our_level},dioxus-cli={our_level},manganis-cli-support={our_level}",
our_level = if args.verbosity.verbose {
"debug"
} else {
"info"
}
))
};
let json_filter = tracing_subscriber::filter::filter_fn(move |meta| {
if meta.fields().len() == 1 && meta.fields().iter().next().unwrap().name() == "json" {
return args.verbosity.json_output;
}
true
});
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(args.verbosity.verbose)
.fmt_fields(
format::debug_fn(move |writer, field, value| {
if field.name() == "json" && !args.verbosity.json_output {
return Ok(());
}
write!(writer, "{}", format_field(field.name(), value))
})
.delimited(" "),
)
.with_timer(PrettyUptime::default());
let fmt_layer = if args.verbosity.json_output {
fmt_layer.json().flatten_event(true).boxed()
} else {
fmt_layer.boxed()
};
let sub = tracing_subscriber::registry()
.with(filter)
.with(json_filter)
.with(FileAppendLayer::new())
.with(fmt_layer);
#[cfg(feature = "tokio-console")]
let sub = sub.with(console_subscriber::spawn());
sub.init();
args
}
/// Get a handle to the trace controller. /// Get a handle to the trace controller.
pub fn redirect() -> Self { pub fn redirect() -> Self {
let (tui_tx, tui_rx) = unbounded(); let (tui_tx, tui_rx) = unbounded();
TUI_ENABLED.store(true, Ordering::SeqCst);
TUI_TX.set(tui_tx.clone()).unwrap(); TUI_TX.set(tui_tx.clone()).unwrap();
Self { tui_rx } Self { tui_rx }
} }
@ -65,54 +136,13 @@ impl TraceController {
ServeUpdate::TracingLog { log } ServeUpdate::TracingLog { log }
} }
pub(crate) fn shutdown(&self) { fn initialize_for_serve() {
TUI_ENABLED.store(false, Ordering::SeqCst); let filter = EnvFilter::new("error,dx=trace,dioxus-cli=trace,manganis-cli-support=trace");
}
/// Build tracing infrastructure. tracing_subscriber::registry()
pub fn initialize() {
let mut filter =
EnvFilter::new("error,dx=trace,dioxus-cli=debug,manganis-cli-support=debug");
if env::var(LOG_ENV).is_ok() {
filter = EnvFilter::from_env(LOG_ENV);
}
// Log file
let log_path = log_path();
_ = std::fs::write(&log_path, "");
let file_append_layer = match FileAppendLayer::new(log_path) {
Ok(f) => Some(f),
Err(e) => {
tracing::error!(dx_src = ?TraceSrc::Dev, err = ?e, "failed to init log file");
None
}
};
// Build CLI layer
let cli_layer = CLILayer;
// Build fmt layer
let fmt_layer = tracing_subscriber::fmt::layer()
.fmt_fields(
format::debug_fn(|writer, field, value| {
write!(writer, "{}", format_field(field.name(), value))
})
.delimited(" "),
)
.with_writer(Mutex::new(FmtLogWriter {}))
.with_timer(tracing_subscriber::fmt::time::time());
let sub = tracing_subscriber::registry()
.with(filter) .with(filter)
.with(file_append_layer) .with(CLILayer {})
.with(cli_layer) .init();
.with(fmt_layer);
#[cfg(feature = "tokio-console")]
let sub = sub.with(console_subscriber::spawn());
sub.init();
} }
} }
@ -120,17 +150,27 @@ impl TraceController {
/// ///
/// This layer returns on any error allowing the cli to continue work /// This layer returns on any error allowing the cli to continue work
/// despite failing to log to a file. This helps in case of permission errors and similar. /// despite failing to log to a file. This helps in case of permission errors and similar.
struct FileAppendLayer { pub(crate) struct FileAppendLayer {
file_path: PathBuf, file_path: PathBuf,
buffer: Mutex<String>, buffer: Mutex<String>,
} }
impl FileAppendLayer { impl FileAppendLayer {
pub fn new(file_path: PathBuf) -> io::Result<Self> { fn new() -> Self {
Ok(Self { let file_path = Self::log_path();
if !file_path.exists() {
_ = std::fs::write(&file_path, "");
}
Self {
file_path, file_path,
buffer: Mutex::new(String::new()), buffer: Mutex::new(String::new()),
}) }
}
pub(crate) fn log_path() -> PathBuf {
std::env::temp_dir().join(LOG_FILE_NAME)
} }
} }
@ -194,15 +234,6 @@ where
let mut visitor = CollectVisitor::new(); let mut visitor = CollectVisitor::new();
event.record(&mut visitor); event.record(&mut visitor);
// If the TUI output is disabled we let fmt subscriber handle the logs
// EXCEPT for cargo logs which we just print.
if !TUI_ENABLED.load(Ordering::SeqCst) {
if visitor.source == TraceSrc::Cargo {
println!("{}", visitor.message);
}
return;
}
let meta = event.metadata(); let meta = event.metadata();
let level = meta.level(); let level = meta.level();
@ -217,14 +248,11 @@ where
visitor.source = TraceSrc::Dev; visitor.source = TraceSrc::Dev;
} }
TUI_TX _ = TUI_TX
.get() .get()
.unwrap() .unwrap()
.unbounded_send(TraceMsg::text(visitor.source, *level, final_msg)) .unbounded_send(TraceMsg::text(visitor.source, *level, final_msg));
.unwrap();
} }
// TODO: support spans? structured tui log display?
} }
/// A record visitor that collects dx-specific info and user-provided fields for logging consumption. /// A record visitor that collects dx-specific info and user-provided fields for logging consumption.
@ -239,7 +267,6 @@ impl CollectVisitor {
Self { Self {
message: String::new(), message: String::new(),
source: TraceSrc::Unknown, source: TraceSrc::Unknown,
fields: HashMap::new(), fields: HashMap::new(),
} }
} }
@ -266,26 +293,6 @@ impl Visit for CollectVisitor {
} }
} }
struct FmtLogWriter {}
impl Write for FmtLogWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if !TUI_ENABLED.load(Ordering::SeqCst) {
return std::io::stdout().write(buf);
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
if !TUI_ENABLED.load(Ordering::SeqCst) {
std::io::stdout().flush()?;
}
Ok(())
}
}
/// Formats a tracing field and value, removing any internal fields from the final output. /// Formats a tracing field and value, removing any internal fields from the final output.
fn format_field(field_name: &str, value: &dyn Debug) -> String { fn format_field(field_name: &str, value: &dyn Debug) -> String {
let mut out = String::new(); let mut out = String::new();
@ -398,3 +405,26 @@ impl Display for TraceSrc {
} }
} }
} }
/// Retrieve and print the relative elapsed wall-clock time since an epoch.
///
/// The `Default` implementation for `Uptime` makes the epoch the current time.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PrettyUptime {
epoch: Instant,
}
impl Default for PrettyUptime {
fn default() -> Self {
Self {
epoch: Instant::now(),
}
}
}
impl FormatTime for PrettyUptime {
fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
let e = self.epoch.elapsed();
write!(w, "{:4}.{:2}s", e.as_secs(), e.subsec_millis())
}
}

View file

@ -4,7 +4,7 @@
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
mod assets; mod assets;
mod builder; mod build;
mod bundle_utils; mod bundle_utils;
mod cli; mod cli;
mod config; mod config;
@ -13,65 +13,65 @@ mod dx_build_info;
mod error; mod error;
mod fastfs; mod fastfs;
mod filemap; mod filemap;
mod logging;
mod metadata; mod metadata;
mod platform; mod platform;
mod profiles; mod profiles;
mod rustup; mod rustup;
mod serve; mod serve;
mod settings; mod settings;
mod slog;
mod tooling; mod tooling;
mod tracer;
pub(crate) use builder::*; pub(crate) use build::*;
pub(crate) use cli::*; pub(crate) use cli::*;
pub(crate) use config::*; pub(crate) use config::*;
pub(crate) use dioxus_crate::*; pub(crate) use dioxus_crate::*;
pub(crate) use dioxus_dx_wire_format::*;
pub(crate) use error::*; pub(crate) use error::*;
pub(crate) use filemap::*; pub(crate) use filemap::*;
pub(crate) use logging::*;
pub(crate) use platform::*; pub(crate) use platform::*;
pub(crate) use rustup::*; pub(crate) use rustup::*;
pub(crate) use settings::*; pub(crate) use settings::*;
pub(crate) use tracer::*;
use anyhow::Context;
use clap::Parser;
use Commands::*;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() {
// If we're being ran as a linker (likely from ourselves), we want to act as a linker instead. // 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() { if let Some(link_action) = link::LinkAction::from_env() {
return link_action.run(); return link_action.run();
} }
// Start the tracer so it captures logs from the build engine before we start the builder let args = TraceController::initialize();
TraceController::initialize(); let result = match args.action {
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::Check(opts) => opts.check().await,
Commands::Clean(opts) => opts.clean().await,
Commands::Build(opts) => opts.run_cmd().await,
Commands::Serve(opts) => opts.serve().await,
Commands::Bundle(opts) => opts.bundle().await,
Commands::Run(opts) => opts.run().await,
Commands::Doctor(opts) => opts.run().await,
};
match Cli::parse().action { // Provide a structured output for third party tools that can consume the output of the CLI
Translate(opts) => opts match result {
.translate() Ok(output) => {
.context("⛔️ Translation of HTML into RSX failed:"), tracing::debug!(json = ?output);
}
Err(err) => {
tracing::error!(
?err,
json = ?StructuredOutput::Error {
message: format!("{err:?}"),
},
);
New(opts) => opts.create().context("🚫 Creating new project failed:"), std::process::exit(1);
}
Init(opts) => opts.init().context("🚫 Initializing a new project failed:"), };
Config(opts) => opts.config().context("🚫 Configuring new project failed:"),
Autoformat(opts) => opts.autoformat().context("🚫 Error autoformatting RSX:"),
Check(opts) => opts.check().await.context("🚫 Error checking RSX:"),
Clean(opts) => opts.clean().context("🚫 Cleaning project failed:"),
Build(mut opts) => opts.build_it().await.context("🚫 Building project failed:"),
Serve(opts) => opts.serve().await.context("🚫 Serving project failed:"),
Bundle(opts) => opts.bundle().await.context("🚫 Bundling project failed:"),
Run(opts) => opts.run().await.context("🚫 Running project failed:"),
Doctor(opts) => opts.run().await.context("🚫 Checking project failed:"),
}
} }

View file

@ -174,10 +174,7 @@ impl Platform {
Some(Platform::Linux) Some(Platform::Linux)
} }
} }
"mobile" => { "mobile" => None,
tracing::warn!("Could not autodetect mobile platform. Mobile platforms must be explicitly specified. Pass `--platform ios` or `--platform android` instead.");
None
}
"liveview" => Some(Platform::Liveview), "liveview" => Some(Platform::Liveview),
"server" => Some(Platform::Server), "server" => Some(Platform::Server),
_ => None, _ => None,

View file

@ -68,6 +68,7 @@ impl AppHandle {
// These need to be stable within a release version (ie 0.6.0) // These need to be stable within a release version (ie 0.6.0)
let mut envs = vec![ let mut envs = vec![
("DIOXUS_CLI_ENABLED", "true".to_string()), ("DIOXUS_CLI_ENABLED", "true".to_string()),
("RUST_BACKTRACE", "1".to_string()),
( (
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV, dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
devserver_ip.to_string(), devserver_ip.to_string(),
@ -116,7 +117,7 @@ impl AppHandle {
// https://developer.android.com/studio/run/emulator-commandline // https://developer.android.com/studio/run/emulator-commandline
Platform::Android => { Platform::Android => {
tracing::error!("Android is not yet supported, sorry!"); self.open_android_sim(envs).await?;
None None
} }
@ -159,7 +160,7 @@ impl AppHandle {
// we won't actually be using the build dir. // we won't actually be using the build dir.
let asset_dir = match self.runtime_asst_dir.as_ref() { let asset_dir = match self.runtime_asst_dir.as_ref() {
Some(dir) => dir.to_path_buf().join("assets/"), Some(dir) => dir.to_path_buf().join("assets/"),
None => self.app.asset_dir(), None => self.app.build.asset_dir(),
}; };
tracing::debug!("Hotreloading asset {changed_file:?} in target {asset_dir:?}"); tracing::debug!("Hotreloading asset {changed_file:?} in target {asset_dir:?}");
@ -237,13 +238,16 @@ impl AppHandle {
/// TODO(jon): we should probably check if there's a simulator running before trying to install, /// TODO(jon): we should probably check if there's a simulator running before trying to install,
/// and open the simulator if we have to. /// and open the simulator if we have to.
async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<Child> { async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
tracing::debug!("Installing app to simulator {:?}", self.app.app_dir()); tracing::debug!(
"Installing app to simulator {:?}",
self.app.build.root_dir()
);
let res = Command::new("xcrun") let res = Command::new("xcrun")
.arg("simctl") .arg("simctl")
.arg("install") .arg("install")
.arg("booted") .arg("booted")
.arg(self.app.app_dir()) .arg(self.app.build.root_dir())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.output() .output()
@ -262,15 +266,13 @@ impl AppHandle {
.arg("launch") .arg("launch")
.arg("--console") .arg("--console")
.arg("booted") .arg("booted")
.arg("com.dioxuslabs") .arg(self.app.bundle_identifier())
.envs(ios_envs) .envs(ios_envs)
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.kill_on_drop(true) .kill_on_drop(true)
.spawn()?; .spawn()?;
tracing::debug!("Launched app on simulator with exit code: {child:?}");
Ok(child) Ok(child)
} }
@ -309,7 +311,7 @@ impl AppHandle {
// # xcrun devicectl device process resume --device "${DEVICE_UUID}" --pid "${STATUS_PID}" > "${XCRUN_DEVICE_PROCESS_RESUME_LOG_DIR}" 2>&1 // # xcrun devicectl device process resume --device "${DEVICE_UUID}" --pid "${STATUS_PID}" > "${XCRUN_DEVICE_PROCESS_RESUME_LOG_DIR}" 2>&1
use serde_json::Value; use serde_json::Value;
let app_path = self.app.app_dir(); let app_path = self.app.build.root_dir();
install_app(&app_path).await?; install_app(&app_path).await?;
@ -448,4 +450,32 @@ impl AppHandle {
unimplemented!("dioxus-cli doesn't support ios devices yet.") unimplemented!("dioxus-cli doesn't support ios devices yet.")
} }
async fn open_android_sim(&self, envs: Vec<(&str, String)>) -> Result<()> {
// Install
// adb install -r app-debug.apk
let _output = Command::new("adb")
.arg("install")
.arg("-r")
.arg(self.app.apk_path())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.output()
.await?;
// adb shell am start -n com.example.androidfinal/com.example.androidfinal.MainActivity
let _output = Command::new("adb")
.arg("shell")
.arg("am")
.arg("start")
.arg("-n")
.arg("com.example.androidfinal/com.example.androidfinal.MainActivity")
.envs(envs)
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.output()
.await?;
Ok(())
}
} }

View file

@ -1,7 +1,4 @@
use crate::{ use crate::{BuildUpdate, Builder, Error, Platform, Result, ServeArgs, TraceController, TraceSrc};
BuildUpdate, Builder, DioxusCrate, Error, Platform, Result, ServeArgs, TraceController,
TraceSrc,
};
mod ansi_buffer; mod ansi_buffer;
mod detect; mod detect;
@ -40,9 +37,13 @@ pub(crate) use watcher::*;
/// - I'd love to be able to configure the CLI while it's running so we can change settings on the fly. /// - 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 /// - 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. /// to a dynamic one on the fly.
pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()> { pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
// Redirect all logging the cli logger
let mut tracer = TraceController::redirect(); let mut tracer = TraceController::redirect();
// Load the krate and resolve the server args against it - this might log so do it after we turn on the tracer first
let krate = args.load_krate()?;
// Note that starting the builder will queue up a build immediately // Note that starting the builder will queue up a build immediately
let mut builder = Builder::start(&krate, args.build_args())?; let mut builder = Builder::start(&krate, args.build_args())?;
let mut devserver = WebServer::start(&krate, &args)?; let mut devserver = WebServer::start(&krate, &args)?;
@ -53,15 +54,15 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
// This is our default splash screen. We might want to make this a fancier splash screen in the future // 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 // Also, these commands might not be the most important, but it's all we've got enabled right now
tracing::info!( tracing::info!(
r#"Serving your Dioxus app: {} 🚀 r#"-----------------------------------------------------------------
Serving your Dioxus app: {} 🚀
- Press `ctrl+c` to exit the server Press `ctrl+c` to exit the server
- Press `r` to rebuild the app Press `r` to rebuild the app
- Press `o` to open the app Press `o` to open the app
- Press `t` to toggle cargo output Press `v` to toggle verbose logging
- Press `/` for more commands and shortcuts Press `/` for more commands and shortcuts
Learn more at https://dioxuslabs.com/learn/0.6/getting_started
Learn more at https://dioxuslabs.com/learn/0.6/getting_started"#, ----------------------------------------------------------------"#,
krate.executable_name() krate.executable_name()
); );
@ -146,7 +147,7 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
screen.new_build_update(&update); screen.new_build_update(&update);
// And then update the websocketed clients with the new build status in case they want it // And then update the websocketed clients with the new build status in case they want it
devserver.new_build_update(&update).await; devserver.new_build_update(&update, &builder).await;
// And then open the app if it's ready // 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: there might be more things to do here that require coordination with other pieces of the CLI
@ -213,6 +214,7 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
// `Full rebuild:` to line up with // `Full rebuild:` to line up with
// `Hotreloading:` to keep the alignment during long edit sessions // `Hotreloading:` to keep the alignment during long edit sessions
tracing::info!("Full rebuild: triggered manually"); tracing::info!("Full rebuild: triggered manually");
builder.rebuild(args.build_arguments.clone()); builder.rebuild(args.build_arguments.clone());
runner.file_map.force_rebuild(); runner.file_map.force_rebuild();
devserver.start_build().await devserver.start_build().await
@ -248,7 +250,6 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
_ = devserver.shutdown().await; _ = devserver.shutdown().await;
_ = screen.shutdown(); _ = screen.shutdown();
builder.abort_all(); builder.abort_all();
tracer.shutdown();
if let Err(err) = err { if let Err(err) = err {
eprintln!("Exiting with error: {}", err); eprintln!("Exiting with error: {}", err);

View file

@ -99,8 +99,8 @@ impl Output {
more_modal_open: false, more_modal_open: false,
pending_logs: VecDeque::new(), pending_logs: VecDeque::new(),
throbber: RefCell::new(throbber_widgets_tui::ThrobberState::default()), throbber: RefCell::new(throbber_widgets_tui::ThrobberState::default()),
trace: cfg.trace, trace: crate::logging::VERBOSITY.get().unwrap().trace,
verbose: cfg.verbose, verbose: crate::logging::VERBOSITY.get().unwrap().verbose,
tick_animation: false, tick_animation: false,
tick_interval: { tick_interval: {
let mut interval = tokio::time::interval(Duration::from_millis(TICK_RATE_MS)); let mut interval = tokio::time::interval(Duration::from_millis(TICK_RATE_MS));
@ -152,10 +152,8 @@ impl Output {
.execute(DisableBracketedPaste)?; .execute(DisableBracketedPaste)?;
disable_raw_mode()?; disable_raw_mode()?;
// print a few lines to not cut off the output // print a line to force the cursor down (no tearing)
for _ in 0..3 { println!();
println!();
}
} }
Ok(()) Ok(())
@ -331,10 +329,12 @@ impl Output {
/// we won't be drawing it. /// 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: &BuildUpdate) {
match update { match update {
BuildUpdate::CompilerMessage { .. } => {} BuildUpdate::Progress {
BuildUpdate::Progress { .. } => self.tick_animation = true, stage: BuildStage::Starting { .. },
} => self.tick_animation = true,
BuildUpdate::BuildReady { .. } => self.tick_animation = false, BuildUpdate::BuildReady { .. } => self.tick_animation = false,
BuildUpdate::BuildFailed { .. } => self.tick_animation = false, BuildUpdate::BuildFailed { .. } => self.tick_animation = false,
_ => {}
} }
} }
@ -381,7 +381,7 @@ impl Output {
let mut area = frame.area(); let mut area = frame.area();
area.width = area.width.clamp(0, VIEWPORT_MAX_WIDTH); area.width = area.width.clamp(0, VIEWPORT_MAX_WIDTH);
let [_top, body, bottom] = Layout::vertical([ let [_top, body, _bottom] = Layout::vertical([
Constraint::Length(1), Constraint::Length(1),
Constraint::Fill(1), Constraint::Fill(1),
Constraint::Length(1), Constraint::Length(1),
@ -391,7 +391,6 @@ impl Output {
self.render_borders(frame, area); self.render_borders(frame, area);
self.render_body(frame, body, state); self.render_body(frame, body, state);
self.render_bottom_row(frame, bottom, state);
self.render_body_title(frame, _top, state); self.render_body_title(frame, _top, state);
} }
@ -512,6 +511,7 @@ impl Output {
BuildStage::Failed => lines.push("Failed".red()), BuildStage::Failed => lines.push("Failed".red()),
BuildStage::Aborted => lines.push("Aborted".red()), BuildStage::Aborted => lines.push("Aborted".red()),
BuildStage::Restarting => lines.push("Restarting".yellow()), BuildStage::Restarting => lines.push("Restarting".yellow()),
_ => {}
}; };
frame.render_widget(Line::from(lines), status_line); frame.render_widget(Line::from(lines), status_line);
@ -712,23 +712,6 @@ impl Output {
); );
} }
/// Render the version number on the bottom right
fn render_bottom_row(&self, frame: &mut Frame, area: Rect, _state: RenderState) {
// Split the area into two chunks
let row = Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area);
frame.render_widget(
Paragraph::new(Line::from(vec![
// "🧬 dx".dark_gray(),
// " ".dark_gray(),
// self.dx_version.as_str().dark_gray(),
// " ".dark_gray(),
]))
.right_aligned(),
row[1],
);
}
/// Render borders around the terminal, forcing an inner clear while we're at it /// Render borders around the terminal, forcing an inner clear while we're at it
fn render_borders(&self, frame: &mut Frame, area: Rect) { fn render_borders(&self, frame: &mut Frame, area: Rect) {
frame.render_widget(ratatui::widgets::Clear, area); frame.render_widget(ratatui::widgets::Clear, area);
@ -784,7 +767,7 @@ impl Output {
// Render the log into an ansi string // Render the log into an ansi string
// We're going to add some metadata to it like the timestamp and source and then dump it to the raw ansi sequences we need to send to crossterm // We're going to add some metadata to it like the timestamp and source and then dump it to the raw ansi sequences we need to send to crossterm
let output_sequence = tracemsg_to_ansi_string(log, term_size.width); let output_sequence = Self::tracemsg_to_ansi_string(log, term_size.width);
// Get the lines of the output sequence and their overflow // Get the lines of the output sequence and their overflow
let lines = output_sequence.lines().collect::<Vec<_>>(); let lines = output_sequence.lines().collect::<Vec<_>>();
@ -863,94 +846,94 @@ impl Output {
false => VIEWPORT_HEIGHT_SMALL, false => VIEWPORT_HEIGHT_SMALL,
} }
} }
}
fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String { fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String {
let rendered = match log.content { let rendered = match log.content {
TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(), TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(),
TraceContent::Text(text) => text, TraceContent::Text(text) => text,
}; };
// Create a paragraph widget using the log line itself // Create a paragraph widget using the log line itself
// From here on out, we want to work with the escaped ansi string and the "real lines" to be printed // From here on out, we want to work with the escaped ansi string and the "real lines" to be printed
// //
// We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them // We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them
// dark gray, just for readability. // dark gray, just for readability.
// //
// todo(jon): refactor this out to accept any widget, not just paragraphs // todo(jon): refactor this out to accept any widget, not just paragraphs
let paragraph = Paragraph::new({ let paragraph = Paragraph::new({
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use chrono::Timelike; use chrono::Timelike;
let mut text = Text::default(); let mut text = Text::default();
for (idx, raw_line) in rendered.lines().enumerate() { for (idx, raw_line) in rendered.lines().enumerate() {
let line_as_text = raw_line.into_text().unwrap(); let line_as_text = raw_line.into_text().unwrap();
let is_pretending_to_be_frame = raw_line let is_pretending_to_be_frame = raw_line
.chars() .chars()
.all(|c| c == '=' || c == '-' || c == ' ' || c == '─'); .all(|c| c == '=' || c == '-' || c == ' ' || c == '─');
for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() { for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() {
let mut out_line = if idx == 0 && subline_idx == 0 { let mut out_line = if idx == 0 && subline_idx == 0 {
let mut formatted_line = Line::default(); let mut formatted_line = Line::default();
formatted_line.push_span( formatted_line.push_span(
Span::raw(format!( Span::raw(format!(
"{:02}:{:02}:{:02} ", "{:02}:{:02}:{:02} ",
log.timestamp.hour(), log.timestamp.hour(),
log.timestamp.minute(), log.timestamp.minute(),
log.timestamp.second() log.timestamp.second()
)) ))
.dark_gray(), .dark_gray(),
); );
formatted_line.push_span( formatted_line.push_span(
Span::raw(format!( Span::raw(format!(
"[{src}] {padding}", "[{src}] {padding}",
src = log.source, src = log.source,
padding = padding =
" ".repeat(3usize.saturating_sub(log.source.to_string().len())) " ".repeat(3usize.saturating_sub(log.source.to_string().len()))
)) ))
.style(match log.source { .style(match log.source {
TraceSrc::App(_platform) => Style::new().blue(), TraceSrc::App(_platform) => Style::new().blue(),
TraceSrc::Dev => Style::new().magenta(), TraceSrc::Dev => Style::new().magenta(),
TraceSrc::Build => Style::new().yellow(), TraceSrc::Build => Style::new().yellow(),
TraceSrc::Bundle => Style::new().magenta(), TraceSrc::Bundle => Style::new().magenta(),
TraceSrc::Cargo => Style::new().yellow(), TraceSrc::Cargo => Style::new().yellow(),
TraceSrc::Unknown => Style::new().gray(), TraceSrc::Unknown => Style::new().gray(),
}), }),
); );
for span in line.spans { for span in line.spans {
formatted_line.push_span(span); formatted_line.push_span(span);
}
formatted_line
} else {
line
};
if is_pretending_to_be_frame {
out_line = out_line.dark_gray();
} }
formatted_line text.lines.push(out_line);
} else {
line
};
if is_pretending_to_be_frame {
out_line = out_line.dark_gray();
} }
text.lines.push(out_line);
} }
}
text text
}); });
// We want to get the escaped ansii string and then by dumping the paragraph as ascii codes (again) // We want to get the escaped ansii string and then by dumping the paragraph as ascii codes (again)
// //
// This is important because the line_count method on paragraph takes into account the width of these codes // This is important because the line_count method on paragraph takes into account the width of these codes
// the 3000 clip width is to bound log lines to a reasonable memory usage // the 3000 clip width is to bound log lines to a reasonable memory usage
// We could consider reusing this buffer since it's a lot to allocate, but log printing is not the // We could consider reusing this buffer since it's a lot to allocate, but log printing is not the
// slowest thing in the world and allocating is pretty fast... // slowest thing in the world and allocating is pretty fast...
// //
// After we've dumped the ascii out, we want to call "trim_end" which ensures we don't attempt // After we've dumped the ascii out, we want to call "trim_end" which ensures we don't attempt
// to print extra characters as lines, since AnsiStringBuffer will in fact attempt to print empty // to print extra characters as lines, since AnsiStringBuffer will in fact attempt to print empty
// cells as characters. That might not actually be important, but we want to shrink the buffer // cells as characters. That might not actually be important, but we want to shrink the buffer
// before printing it // before printing it
let line_count = paragraph.line_count(term_width); let line_count = paragraph.line_count(term_width);
AnsiStringBuffer::new(3000, line_count as u16).render(&paragraph) AnsiStringBuffer::new(3000, line_count as u16).render(&paragraph)
}
} }

View file

@ -111,6 +111,16 @@ impl AppRunner {
// todo(jon): we should allow rebinding to the same port in fullstack itself // todo(jon): we should allow rebinding to the same port in fullstack itself
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
// Add some cute logging
if self.builds_opened == 0 {
tracing::info!(
"Build completed successfully in {:?}ms, launching app! 💫",
app.app.time_taken.as_millis()
);
} else {
tracing::info!("Build completed in {:?}ms", app.app.time_taken.as_millis());
}
// Start the new app before we kill the old one to give it a little bit of time // Start the new app before we kill the old one to give it a little bit of time
let mut handle = AppHandle::new(app).await?; let mut handle = AppHandle::new(app).await?;
handle handle
@ -120,6 +130,7 @@ impl AppRunner {
self.builds_opened == 0 && should_open_web, self.builds_opened == 0 && should_open_web,
) )
.await?; .await?;
self.builds_opened += 1; self.builds_opened += 1;
self.running.insert(platform, handle); self.running.insert(platform, handle);
@ -264,17 +275,17 @@ impl AppRunner {
.arg("simctl") .arg("simctl")
.arg("get_app_container") .arg("get_app_container")
.arg("booted") .arg("booted")
.arg("com.dioxuslabs") .arg(runner.app.bundle_identifier())
.output() .output()
.await; .await;
if let Ok(res) = res { if let Ok(res) = res {
tracing::debug!("Using runtime asset dir: {:?}", res); tracing::trace!("Using runtime asset dir: {:?}", res);
if let Ok(out) = String::from_utf8(res.stdout) { if let Ok(out) = String::from_utf8(res.stdout) {
let out = out.trim(); let out = out.trim();
tracing::debug!("Setting Runtime asset dir: {out:?}"); tracing::trace!("Setting Runtime asset dir: {out:?}");
runner.runtime_asst_dir = Some(PathBuf::from(out)); runner.runtime_asst_dir = Some(PathBuf::from(out));
} }
} }

View file

@ -204,7 +204,11 @@ impl WebServer {
} }
/// Sends an updated build status to all clients. /// 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: &BuildUpdate,
builder: &super::Builder,
) {
match update { match update {
BuildUpdate::Progress { stage } => { BuildUpdate::Progress { stage } => {
// Todo(miles): wire up more messages into the splash screen UI // Todo(miles): wire up more messages into the splash screen UI
@ -220,11 +224,13 @@ impl WebServer {
krate, krate,
.. ..
} => { } => {
self.build_status.set(Status::Building { if !builder.is_finished() {
progress: (*current as f64 / *total as f64).clamp(0.0, 1.0), self.build_status.set(Status::Building {
build_message: format!("{krate} compiling"), progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),
}); build_message: format!("{krate} compiling"),
self.send_build_status().await; });
self.send_build_status().await;
}
} }
BuildStage::OptimizingWasm {} => {} BuildStage::OptimizingWasm {} => {}
BuildStage::Aborted => {} BuildStage::Aborted => {}
@ -250,7 +256,7 @@ impl WebServer {
return; return;
} }
tracing::debug!("Sending hotreload to clients {:?}", reload); tracing::trace!("Sending hotreload to clients {:?}", reload);
let msg = DevserverMsg::HotReload(reload); let msg = DevserverMsg::HotReload(reload);
let msg = serde_json::to_string(&msg).unwrap(); let msg = serde_json::to_string(&msg).unwrap();
@ -281,6 +287,8 @@ impl WebServer {
/// Tells all clients to reload if possible for new changes. /// Tells all clients to reload if possible for new changes.
pub(crate) async fn send_reload_command(&mut self) { pub(crate) async fn send_reload_command(&mut self) {
tracing::trace!("Sending reload to toast");
tokio::time::sleep(std::time::Duration::from_millis(500)).await; tokio::time::sleep(std::time::Duration::from_millis(500)).await;
self.build_status.set(Status::Ready); self.build_status.set(Status::Ready);

1
packages/cli/src/slog.rs Normal file
View file

@ -0,0 +1 @@

View file

@ -19,7 +19,6 @@ quote = { workspace = true }
[features] [features]
default = [] default = []
fullstack = [] fullstack = []
static-generation = []
desktop = [] desktop = []
mobile = [] mobile = []
web = [] web = []

View file

@ -33,6 +33,5 @@ define_config_macro!(web if feature = "web");
define_config_macro!(desktop if feature = "desktop"); define_config_macro!(desktop if feature = "desktop");
define_config_macro!(mobile if feature = "mobile"); define_config_macro!(mobile if feature = "mobile");
define_config_macro!(fullstack if feature = "fullstack"); define_config_macro!(fullstack if feature = "fullstack");
define_config_macro!(static_generation if feature = "static-generation");
define_config_macro!(ssr if feature = "ssr"); define_config_macro!(ssr if feature = "ssr");
define_config_macro!(liveview if feature = "liveview"); define_config_macro!(liveview if feature = "liveview");

View file

@ -0,0 +1,3 @@
{
// "rust-analyzer.cargo.target": "aarch64-linux-android"
}

View file

@ -83,6 +83,10 @@ objc_id = "0.1.1"
# use rustls on android # use rustls on android
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
tokio-tungstenite = { workspace = true, optional = true, features = ["rustls"]} tokio-tungstenite = { workspace = true, optional = true, features = ["rustls"]}
jni = "0.21.1"
ndk = { version = "0.9.0" }
ndk-sys = { version = "0.6.0" }
ndk-context = { version = "0.1.1" }
# use native tls on other platforms # use native tls on other platforms
[target.'cfg(not(target_os = "android"))'.dependencies] [target.'cfg(not(target_os = "android"))'.dependencies]
@ -97,11 +101,12 @@ objc = "0.2.7"
lazy-js-bundle = { workspace = true } lazy-js-bundle = { workspace = true }
[features] [features]
default = ["tokio_runtime", "wry/objc-exception", "devtools"] default = ["tokio_runtime", "exception", "devtools"]
tokio_runtime = ["dep:tokio"] tokio_runtime = ["dep:tokio"]
fullscreen = ["wry/fullscreen"] fullscreen = ["wry/fullscreen"]
transparent = ["wry/transparent"] transparent = ["wry/transparent"]
devtools = ["wry/devtools", "dep:dioxus-devtools", "dioxus-signals"] devtools = ["wry/devtools", "dep:dioxus-devtools", "dioxus-signals"]
exception = ["wry/objc-exception"]
gnu = [] gnu = []
[package.metadata.docs.rs] [package.metadata.docs.rs]

View file

@ -75,7 +75,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
webview.dom.in_runtime(|| { webview.dom.in_runtime(|| {
ScopeId::ROOT.in_runtime(|| { ScopeId::ROOT.in_runtime(|| {
let e = eval( let e = eval(
r#" r#"
const xPos = await dioxus.recv(); const xPos = await dioxus.recv();
const yPos = await dioxus.recv(); const yPos = await dioxus.recv();
window.interpreter.handleWindowsDragOver(xPos, yPos) window.interpreter.handleWindowsDragOver(xPos, yPos)
@ -121,7 +121,9 @@ pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) -> !
} }
#[cfg(not(feature = "tokio_runtime"))] #[cfg(not(feature = "tokio_runtime"))]
launch_virtual_dom_blocking(virtual_dom, desktop_config); {
launch_virtual_dom_blocking(virtual_dom, desktop_config);
}
} }
/// Launches the WebView and runs the event loop, with configuration and root props. /// Launches the WebView and runs the event loop, with configuration and root props.

View file

@ -84,6 +84,17 @@ fn serve_asset(request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>> {
.as_ref(), .as_ref(),
); );
// Attempt to serve from the asset dir on android using its loader
#[cfg(target_os = "android")]
{
if let Some(asset) = to_java_load_asset(request.uri().path()) {
return Ok(Response::builder()
.header("Content-Type", get_mime_by_ext(&uri_path))
.header("Access-Control-Allow-Origin", "*")
.body(asset)?);
}
}
// If the asset doesn't exist, or starts with `/assets/`, then we'll try to serve out of the bundle // If the asset doesn't exist, or starts with `/assets/`, then we'll try to serve out of the bundle
// This lets us handle both absolute and relative paths without being too "special" // This lets us handle both absolute and relative paths without being too "special"
// It just means that our macos bundle is a little "special" because we need to place an `assets` // It just means that our macos bundle is a little "special" because we need to place an `assets`
@ -261,3 +272,42 @@ fn get_mime_by_ext(trimmed: &Path) -> &'static str {
None => "application/octet-stream", None => "application/octet-stream",
} }
} }
#[cfg(target_os = "android")]
pub(crate) fn to_java_load_asset(filepath: &str) -> Option<Vec<u8>> {
use std::{io::Read, ptr::NonNull};
let ctx = ndk_context::android_context();
let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap();
let mut env = vm.attach_current_thread().unwrap();
// Query the Asset Manager
let asset_manager_ptr = env
.call_method(
unsafe { jni::objects::JObject::from_raw(ctx.context().cast()) },
"getAssets",
"()Landroid/content/res/AssetManager;",
&[],
)
.expect("Failed to get asset manager")
.l()
.expect("Failed to get asset manager as object");
unsafe {
let asset_manager =
ndk_sys::AAssetManager_fromJava(env.get_native_interface(), *asset_manager_ptr);
let asset_manager = ndk::asset::AssetManager::from_ptr(
NonNull::new(asset_manager).expect("Invalid asset manager"),
);
let normalized = filepath
.trim_start_matches("/assets/")
.trim_start_matches('/');
let cstr = std::ffi::CString::new(normalized).unwrap();
let mut asset = asset_manager.open(&cstr)?;
Some(asset.buffer().unwrap().to_vec())
}
}

View file

@ -37,26 +37,13 @@ pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send +
std::thread::spawn(move || { std::thread::spawn(move || {
let (mut websocket, _req) = match tungstenite::connect(endpoint.clone()) { let (mut websocket, _req) = match tungstenite::connect(endpoint.clone()) {
Ok((websocket, req)) => (websocket, req), Ok((websocket, req)) => (websocket, req),
Err(err) => { Err(_) => return,
eprintln!(
"Failed to connect to devserver at {} because {}",
endpoint, err
);
return;
}
}; };
while let Ok(msg) = websocket.read() { while let Ok(msg) = websocket.read() {
match msg { if let tungstenite::Message::Text(text) = msg {
tungstenite::Message::Text(text) => { if let Ok(msg) = serde_json::from_str(&text) {
if let Ok(msg) = serde_json::from_str(&text) { callback(msg);
callback(msg);
} else {
eprintln!("Failed to parse message from devserver: {:?}", text);
}
}
msg => {
println!("Received a non-text message: {:?}", msg);
} }
} }
} }

View file

@ -24,13 +24,11 @@ dioxus-web = { workspace = true, default-features = false, optional = true }
dioxus-mobile = { workspace = true, optional = true } dioxus-mobile = { workspace = true, optional = true }
dioxus-desktop = { workspace = true, default-features = true, optional = true } dioxus-desktop = { workspace = true, default-features = true, optional = true }
dioxus-fullstack = { workspace = true, default-features = true, optional = true } dioxus-fullstack = { workspace = true, default-features = true, optional = true }
dioxus-static-site-generation = { workspace = true, optional = true }
dioxus-liveview = { workspace = true, optional = true } dioxus-liveview = { workspace = true, optional = true }
dioxus-ssr = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true }
manganis = { workspace = true, optional = true } manganis = { workspace = true, optional = true }
serde = { version = "1.0.136", optional = true } serde = { version = "1.0.136", optional = true }
axum = { workspace = true, optional = true }
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")))'.dependencies] [target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")))'.dependencies]
dioxus-devtools = { workspace = true, optional = true } dioxus-devtools = { workspace = true, optional = true }
@ -55,12 +53,10 @@ router = ["dep:dioxus-router"]
fullstack = ["dep:dioxus-fullstack", "dioxus-config-macro/fullstack", "dep:serde"] fullstack = ["dep:dioxus-fullstack", "dioxus-config-macro/fullstack", "dep:serde"]
desktop = ["dep:dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"] desktop = ["dep:dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
mobile = ["dep:dioxus-mobile", "dioxus-fullstack?/mobile", "dioxus-config-macro/mobile"] mobile = ["dep:dioxus-mobile", "dioxus-fullstack?/mobile", "dioxus-config-macro/mobile"]
web = ["dep:dioxus-web", "dioxus-fullstack?/web", "dioxus-static-site-generation?/web", "dioxus-config-macro/web"] web = ["dep:dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web"]
ssr = ["dep:dioxus-ssr", "dioxus-config-macro/ssr"] ssr = ["dep:dioxus-ssr", "dioxus-config-macro/ssr"]
liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"] liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"]
static-generation = ["dep:dioxus-static-site-generation", "dioxus-config-macro/static-generation"] server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "ssr", "dioxus-liveview?/axum"]
axum = ["server"]
server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "dioxus-static-site-generation?/server", "ssr", "dioxus-liveview?/axum", "dep:axum"]
# This feature just disables the no-renderer-enabled warning # This feature just disables the no-renderer-enabled warning
third-party-renderer = [] third-party-renderer = []
@ -90,6 +86,5 @@ features = [
"hooks", "hooks",
"html", "html",
"liveview", "liveview",
"static-generation",
"server" "server"
] ]

View file

@ -16,6 +16,7 @@ pub struct LaunchBuilder {
} }
pub type LaunchFn = fn(fn() -> Element, Vec<ContextFn>, Vec<Box<dyn Any>>); pub type LaunchFn = fn(fn() -> Element, Vec<ContextFn>, Vec<Box<dyn Any>>);
/// A context function is a Send and Sync closure that returns a boxed trait object /// A context function is a Send and Sync closure that returns a boxed trait object
pub type ContextFn = Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>; pub type ContextFn = Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>;
@ -32,10 +33,9 @@ impl LaunchBuilder {
feature = "mobile", feature = "mobile",
feature = "web", feature = "web",
feature = "fullstack", feature = "fullstack",
feature = "static-generation"
))), ))),
deprecated( deprecated(
note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```" note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, or `fullstack` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
) )
)] )]
pub fn new() -> LaunchBuilder { pub fn new() -> LaunchBuilder {
@ -82,22 +82,6 @@ impl LaunchBuilder {
} }
} }
/// Launch your static site generation application.
#[cfg(all(feature = "static-generation", feature = "server"))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "static-generation", feature = "server")))
)]
pub fn static_generation() -> LaunchBuilder {
LaunchBuilder {
launch_fn: |root, contexts, cfg| {
dioxus_static_site_generation::launch::launch(root, contexts, cfg)
},
contexts: Vec::new(),
configs: Vec::new(),
}
}
/// Launch your fullstack application. /// Launch your fullstack application.
#[cfg(feature = "mobile")] #[cfg(feature = "mobile")]
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))] #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
@ -188,19 +172,10 @@ impl LaunchBuilder {
(self.launch_fn)(app, self.contexts, cfg); (self.launch_fn)(app, self.contexts, cfg);
} }
// Static generation is the only platform that may exit. We can't use the `!` type here
#[cfg(any(feature = "static-generation", feature = "web"))]
/// Launch your application. /// Launch your application.
pub fn launch(self, app: fn() -> Element) { pub fn launch(self, app: fn() -> Element) {
self.launch_inner(app); self.launch_inner(app);
} }
#[cfg(not(any(feature = "static-generation", feature = "web")))]
/// Launch your application.
pub fn launch(self, app: fn() -> Element) -> ! {
self.launch_inner(app);
unreachable!("Launching an application will never exit")
}
} }
/// Re-export the platform we expect the user wants /// Re-export the platform we expect the user wants
@ -209,7 +184,6 @@ impl LaunchBuilder {
/// - `fullstack` /// - `fullstack`
/// - `desktop` /// - `desktop`
/// - `mobile` /// - `mobile`
/// - `static-generation`
/// - `web` /// - `web`
/// - `liveview` /// - `liveview`
mod current_platform { mod current_platform {
@ -227,20 +201,12 @@ mod current_platform {
not(feature = "desktop"), not(feature = "desktop"),
not(all(feature = "fullstack", feature = "server")) not(all(feature = "fullstack", feature = "server"))
))] ))]
pub use dioxus_mobile::launch::*; pub use dioxus_mobile::launch_bindings::*;
#[cfg(all(
all(feature = "static-generation", feature = "server"),
not(all(feature = "fullstack", feature = "server")),
not(feature = "desktop"),
not(feature = "mobile")
))]
pub use dioxus_static_site_generation::launch::*;
#[cfg(all( #[cfg(all(
feature = "web", feature = "web",
not(all(feature = "fullstack", feature = "server")), not(all(feature = "fullstack", feature = "server")),
not(all(feature = "static-generation", feature = "server")), not(all(feature = "server")),
not(feature = "desktop"), not(feature = "desktop"),
not(feature = "mobile"), not(feature = "mobile"),
))] ))]
@ -255,7 +221,7 @@ mod current_platform {
#[cfg(all( #[cfg(all(
feature = "liveview", feature = "liveview",
not(all(feature = "fullstack", feature = "server")), not(all(feature = "fullstack", feature = "server")),
not(all(feature = "static-generation", feature = "server")), not(all(feature = "server")),
not(feature = "desktop"), not(feature = "desktop"),
not(feature = "mobile"), not(feature = "mobile"),
not(feature = "web"), not(feature = "web"),
@ -265,7 +231,7 @@ mod current_platform {
#[cfg(not(any( #[cfg(not(any(
feature = "liveview", feature = "liveview",
all(feature = "fullstack", feature = "server"), all(feature = "fullstack", feature = "server"),
all(feature = "static-generation", feature = "server"), all(feature = "server"),
feature = "desktop", feature = "desktop",
feature = "mobile", feature = "mobile",
feature = "web", feature = "web",
@ -274,7 +240,7 @@ mod current_platform {
root: fn() -> dioxus_core::Element, root: fn() -> dioxus_core::Element,
contexts: Vec<super::ContextFn>, contexts: Vec<super::ContextFn>,
platform_config: Vec<Box<dyn std::any::Any>>, platform_config: Vec<Box<dyn std::any::Any>>,
) -> ! { ) {
#[cfg(feature = "third-party-renderer")] #[cfg(feature = "third-party-renderer")]
panic!("No first party renderer feature enabled. It looks like you are trying to use a third party renderer. You will need to use the launch function from the third party renderer crate."); panic!("No first party renderer feature enabled. It looks like you are trying to use a third party renderer. You will need to use the launch function from the third party renderer crate.");
@ -282,23 +248,11 @@ mod current_platform {
} }
} }
// ! is unstable, so we can't name the type with an alias. Instead we need to generate different variants of items with macros pub fn launch(app: fn() -> Element) {
macro_rules! impl_launch { #[allow(deprecated)]
($($return_type:tt),*) => { LaunchBuilder::new().launch(app)
/// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
pub fn launch(app: fn() -> Element) -> $($return_type)* {
#[allow(deprecated)]
LaunchBuilder::new().launch(app)
}
};
} }
// Static generation is the only platform that may exit. We can't use the `!` type here
#[cfg(any(feature = "static-generation", feature = "web"))]
impl_launch!(());
#[cfg(not(any(feature = "static-generation", feature = "web")))]
impl_launch!(!);
#[cfg(feature = "web")] #[cfg(feature = "web")]
fn web_launch( fn web_launch(
root: fn() -> dioxus_core::Element, root: fn() -> dioxus_core::Element,
@ -306,7 +260,7 @@ fn web_launch(
platform_config: Vec<Box<dyn std::any::Any>>, platform_config: Vec<Box<dyn std::any::Any>>,
) { ) {
// If the server feature is enabled, launch the client with hydration enabled // If the server feature is enabled, launch the client with hydration enabled
#[cfg(any(feature = "static-generation", feature = "fullstack"))] #[cfg(feature = "fullstack")]
{ {
let platform_config = platform_config let platform_config = platform_config
.into_iter() .into_iter()
@ -319,12 +273,11 @@ fn web_launch(
for context in contexts { for context in contexts {
vdom.insert_any_root_context(context()); vdom.insert_any_root_context(context());
} }
#[cfg(feature = "document")] #[cfg(feature = "document")]
{ {
#[cfg(feature = "fullstack")] #[cfg(feature = "fullstack")]
use dioxus_fullstack::document; use dioxus_fullstack::document;
#[cfg(all(feature = "static-generation", not(feature = "fullstack")))]
use dioxus_static_site_generation::document;
let document = std::rc::Rc::new(document::web::FullstackWebDocument) let document = std::rc::Rc::new(document::web::FullstackWebDocument)
as std::rc::Rc<dyn crate::prelude::document::Document>; as std::rc::Rc<dyn crate::prelude::document::Document>;
vdom.provide_root_context(document); vdom.provide_root_context(document);
@ -334,6 +287,7 @@ fn web_launch(
dioxus_web::launch::launch_virtual_dom(factory(), platform_config) dioxus_web::launch::launch_virtual_dom(factory(), platform_config)
} }
#[cfg(not(any(feature = "static-generation", feature = "fullstack")))]
#[cfg(not(any(feature = "fullstack")))]
dioxus_web::launch::launch(root, contexts, platform_config); dioxus_web::launch::launch(root, contexts, platform_config);
} }

View file

@ -19,8 +19,7 @@
//! - `mobile`: enables the mobile platform //! - `mobile`: enables the mobile platform
//! - `web`: enables the web platform. If the fullstack platform is enabled, this will set the fullstack platform to client mode //! - `web`: enables the web platform. If the fullstack platform is enabled, this will set the fullstack platform to client mode
//! - `liveview`: enables the liveview platform //! - `liveview`: enables the liveview platform
//! - `static-generation`: enables the static generation platform. This must be used in combination with the `web` feature for wasm builds and `axum` feature for server builds //! - `server`: enables the server variant of dioxus
//! - `axum`: enables the axum server with static generation or fullstack and sets the platform to server mode
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
@ -119,13 +118,6 @@ pub mod prelude {
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))] #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
pub use dioxus_fullstack::prelude::*; pub use dioxus_fullstack::prelude::*;
#[cfg(all(feature = "static-generation", not(feature = "fullstack")))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "static-generation", not(feature = "fullstack"))))
)]
pub use dioxus_static_site_generation::prelude::*;
#[cfg(feature = "router")] #[cfg(feature = "router")]
#[cfg_attr(docsrs, doc(cfg(feature = "router")))] #[cfg_attr(docsrs, doc(cfg(feature = "router")))]
pub use dioxus_router; pub use dioxus_router;
@ -134,10 +126,6 @@ pub mod prelude {
#[cfg_attr(docsrs, doc(cfg(feature = "router")))] #[cfg_attr(docsrs, doc(cfg(feature = "router")))]
pub use dioxus_router::prelude::*; pub use dioxus_router::prelude::*;
#[cfg(feature = "axum")]
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
pub use axum;
#[cfg(feature = "asset")] #[cfg(feature = "asset")]
#[cfg_attr(docsrs, doc(cfg(feature = "asset")))] #[cfg_attr(docsrs, doc(cfg(feature = "asset")))]
pub use manganis::{self, asset, Asset, ImageAsset, ImageType}; pub use manganis::{self, asset, Asset, ImageAsset, ImageType};
@ -155,10 +143,6 @@ pub use dioxus_router as router;
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))] #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
pub use dioxus_fullstack as fullstack; pub use dioxus_fullstack as fullstack;
#[cfg(feature = "static-generation")]
#[cfg_attr(docsrs, doc(cfg(feature = "static-generation")))]
pub use dioxus_static_site_generation as static_site_generation;
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))] #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
pub use dioxus_desktop as desktop; pub use dioxus_desktop as desktop;

Some files were not shown because too many files have changed in this diff Show more