mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-14 00:17:17 +00:00
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:
parent
8a998e7d49
commit
ac3e33af46
116 changed files with 2369 additions and 6093 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -24,3 +24,7 @@ node_modules/
|
|||
/packages/playwright-report/
|
||||
/packages/playwright/.cache/
|
||||
|
||||
|
||||
# ignore the output of tmps
|
||||
tmp/
|
||||
bundle/
|
||||
|
|
170
Cargo.lock
generated
170
Cargo.lock
generated
|
@ -432,7 +432,7 @@ dependencies = [
|
|||
"base64ct",
|
||||
"blake2",
|
||||
"cpufeatures",
|
||||
"password-hash 0.5.0",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -558,8 +558,8 @@ dependencies = [
|
|||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"zstd 0.13.2",
|
||||
"zstd-safe 7.2.1",
|
||||
"zstd",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2466,12 +2466,6 @@ version = "0.2.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
|
@ -3152,7 +3146,6 @@ dependencies = [
|
|||
name = "dioxus"
|
||||
version = "0.6.0-alpha.4"
|
||||
dependencies = [
|
||||
"axum 0.7.7",
|
||||
"criterion",
|
||||
"dioxus",
|
||||
"dioxus-config-macro",
|
||||
|
@ -3170,7 +3163,6 @@ dependencies = [
|
|||
"dioxus-router",
|
||||
"dioxus-signals",
|
||||
"dioxus-ssr",
|
||||
"dioxus-static-site-generation",
|
||||
"dioxus-web",
|
||||
"env_logger 0.10.2",
|
||||
"futures-util",
|
||||
|
@ -3235,6 +3227,7 @@ dependencies = [
|
|||
"dioxus-core",
|
||||
"dioxus-core-types",
|
||||
"dioxus-devtools-types",
|
||||
"dioxus-dx-wire-format",
|
||||
"dioxus-fullstack",
|
||||
"dioxus-html",
|
||||
"dioxus-rsx",
|
||||
|
@ -3242,8 +3235,6 @@ dependencies = [
|
|||
"dioxus-rsx-rosetta",
|
||||
"dirs",
|
||||
"env_logger 0.11.5",
|
||||
"flate2",
|
||||
"fs_extra",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"handlebars",
|
||||
|
@ -3253,6 +3244,7 @@ dependencies = [
|
|||
"hyper-rustls 0.27.3",
|
||||
"hyper-util",
|
||||
"ignore",
|
||||
"include_dir",
|
||||
"itertools 0.13.0",
|
||||
"krates",
|
||||
"log",
|
||||
|
@ -3272,7 +3264,6 @@ dependencies = [
|
|||
"serde_json",
|
||||
"strum 0.26.3",
|
||||
"syn 2.0.87",
|
||||
"tar",
|
||||
"tauri-bundler",
|
||||
"tauri-utils",
|
||||
"tempfile",
|
||||
|
@ -3294,7 +3285,6 @@ dependencies = [
|
|||
"wasm-bindgen-cli-support",
|
||||
"wasm-bindgen-shared",
|
||||
"wasm-opt",
|
||||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3387,8 +3377,12 @@ dependencies = [
|
|||
"global-hotkey",
|
||||
"http-range",
|
||||
"infer 0.11.0",
|
||||
"jni",
|
||||
"lazy-js-bundle",
|
||||
"muda 0.11.5",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"ndk-sys",
|
||||
"objc",
|
||||
"objc_id",
|
||||
"once_cell",
|
||||
|
@ -3453,6 +3447,15 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-dx-wire-format"
|
||||
version = "0.6.0-alpha.4"
|
||||
dependencies = [
|
||||
"cargo_metadata",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-examples"
|
||||
version = "0.6.0-alpha.4"
|
||||
|
@ -3844,28 +3847,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "dioxus-tailwind"
|
||||
version = "0.0.0"
|
||||
|
@ -5148,15 +5129,6 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "github-pages-static-generation"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"tower 0.4.13",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gix-actor"
|
||||
version = "0.31.5"
|
||||
|
@ -6321,6 +6293,25 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
|
@ -8192,17 +8183,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
|
@ -8244,18 +8224,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "pear"
|
||||
version = "0.2.9"
|
||||
|
@ -9600,14 +9568,6 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "router-static-generation"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpm"
|
||||
version = "0.15.1"
|
||||
|
@ -9635,7 +9595,7 @@ dependencies = [
|
|||
"sha2",
|
||||
"thiserror",
|
||||
"xz2",
|
||||
"zstd 0.13.2",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -10435,14 +10395,6 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74"
|
||||
|
||||
[[package]]
|
||||
name = "simple-static-generation"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.2"
|
||||
|
@ -11142,9 +11094,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.30.6"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833b4d43383d76d5078d72f3acd977f47eb5b6751eb40baa665d13828e7b79df"
|
||||
checksum = "2a93f2c6b8fdaeb7f417bda89b5bc767999745c3052969664ae1fa65892deb7e"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cocoa 0.26.0",
|
||||
|
@ -11874,6 +11826,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
|
@ -11884,12 +11846,15 @@ dependencies = [
|
|||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -13642,18 +13607,10 @@ version = "0.6.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"hmac",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time",
|
||||
"zstd 0.11.2+zstd.1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -13698,32 +13655,13 @@ dependencies = [
|
|||
"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]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe 7.2.1",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
20
Cargo.toml
20
Cargo.toml
|
@ -54,18 +54,13 @@ members = [
|
|||
"packages/server-macro",
|
||||
"packages/signals",
|
||||
"packages/ssr",
|
||||
"packages/static-generation",
|
||||
"packages/lazy-js-bundle",
|
||||
"packages/cli-config",
|
||||
"packages/devtools",
|
||||
"packages/devtools-types",
|
||||
"packages/isrg",
|
||||
"packages/rsx-hotreload",
|
||||
|
||||
# Static generation examples
|
||||
# "packages/static-generation/examples/simple",
|
||||
# "packages/static-generation/examples/router",
|
||||
# "packages/static-generation/examples/github-pages",
|
||||
"packages/dx-wire-format",
|
||||
|
||||
# Playwright tests
|
||||
"packages/playwright-tests/liveview",
|
||||
|
@ -94,9 +89,6 @@ members = [
|
|||
"examples/fullstack-streaming",
|
||||
"examples/fullstack-desktop",
|
||||
"examples/fullstack-auth",
|
||||
"examples/ssg-simple",
|
||||
"examples/ssg-router",
|
||||
"examples/ssg-github-pages",
|
||||
|
||||
# Playwright tests
|
||||
"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-types = { path = "packages/devtools-types", 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-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" }
|
||||
manganis = { path = "packages/manganis/manganis", 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"
|
||||
criterion = { version = "0.5" }
|
||||
walrus = "*"
|
||||
cargo_metadata = "0.18.1"
|
||||
|
||||
# desktop
|
||||
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"
|
||||
infer = "0.16.0"
|
||||
dunce = "1.0.2"
|
||||
|
@ -310,8 +304,8 @@ default = ["desktop"]
|
|||
desktop = ["dioxus/desktop"]
|
||||
liveview = ["dioxus/liveview"]
|
||||
fullstack = ["dioxus/fullstack"]
|
||||
axum = ["dioxus/axum"]
|
||||
server = ["dioxus/axum"]
|
||||
server = ["dioxus/server"]
|
||||
mobile = ["dioxus/mobile"]
|
||||
web = ["dioxus/web"]
|
||||
http = ["dep:reqwest", "dep:http-range"]
|
||||
|
||||
|
|
|
@ -18,5 +18,5 @@ tracing-subscriber = "0.3.17"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/axum"]
|
||||
server = ["dioxus/server"]
|
||||
web = ["dioxus/web"]
|
||||
|
|
|
@ -5,8 +5,6 @@ use async_std::task::sleep;
|
|||
use dioxus::prelude::*;
|
||||
use web_time::Instant;
|
||||
|
||||
const STYLE: Asset = asset!("/examples/assets/clock.css");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
@ -36,7 +34,7 @@ fn app() -> Element {
|
|||
);
|
||||
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: STYLE }
|
||||
document::Stylesheet { href: asset!("/examples/assets/clock.css") }
|
||||
div { id: "app",
|
||||
div { id: "title", "Carpe diem 🎉" }
|
||||
div { id: "clock-display", "{time}" }
|
||||
|
|
|
@ -39,10 +39,8 @@ fn app() -> Element {
|
|||
|
||||
rsx! {
|
||||
for cur_breed in breeds.message.keys().take(20).cloned() {
|
||||
li { key: "{cur_breed}",
|
||||
button { onclick: move |_| breed.set(cur_breed.clone()),
|
||||
"{cur_breed}"
|
||||
}
|
||||
button { onclick: move |_| breed.set(cur_breed.clone()),
|
||||
"{cur_breed}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,11 +53,9 @@ fn app() -> Element {
|
|||
};
|
||||
|
||||
rsx! {
|
||||
h1 { "Select a dog breed!" }
|
||||
div { height: "500px", display: "flex",
|
||||
ul { width: "100px", {breed_list} }
|
||||
div { flex: 1, BreedPic { breed } }
|
||||
}
|
||||
h1 { "Select a dog breed: {breed}" }
|
||||
BreedPic { breed }
|
||||
div { width: "400px", {breed_list} }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,8 +77,10 @@ fn BreedPic(breed: Signal<String>) -> Element {
|
|||
|
||||
match fut.read_unchecked().as_ref() {
|
||||
Some(Ok(resp)) => rsx! {
|
||||
button { onclick: move |_| fut.restart(), "Click to fetch another doggo" }
|
||||
img { max_width: "500px", max_height: "500px", src: "{resp.message}" }
|
||||
div {
|
||||
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" },
|
||||
None => rsx! { "loading image..." },
|
||||
|
|
|
@ -14,6 +14,6 @@ serde = { version = "1.0.159", features = ["derive"] }
|
|||
|
||||
[features]
|
||||
default = []
|
||||
server = ["axum", "dioxus/axum"]
|
||||
server = ["axum", "dioxus/server"]
|
||||
web = ["dioxus/web"]
|
||||
|
||||
|
|
|
@ -20,5 +20,5 @@ once_cell = "1.19.0"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/axum", "dep:tokio"]
|
||||
server = ["dioxus/server", "dep:tokio"]
|
||||
web = ["dioxus/web"]
|
||||
|
|
10
examples/mobile_demo/.gitignore
vendored
10
examples/mobile_demo/.gitignore
vendored
|
@ -1,10 +0,0 @@
|
|||
# Rust
|
||||
target/
|
||||
**/*.rs.bk
|
||||
|
||||
# cargo-mobile2
|
||||
.cargo/
|
||||
/gen
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
4007
examples/mobile_demo/Cargo.lock
generated
4007
examples/mobile_demo/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
|
@ -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.
|
|
@ -1,8 +0,0 @@
|
|||
[app]
|
||||
name = "mobile-demo"
|
||||
stylized-name = "Mobile Demo"
|
||||
domain = "example.com"
|
||||
template-pack = "wry"
|
||||
|
||||
[apple]
|
||||
development-team = "34U4FG9TJ8"
|
|
@ -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>
|
|
@ -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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
4
examples/ssg-github-pages/.gitignore
vendored
4
examples/ssg-github-pages/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
dist
|
||||
target
|
||||
docs
|
||||
.dioxus
|
|
@ -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"]
|
|
@ -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:?}" }
|
||||
}
|
||||
}
|
4
examples/ssg-router/.gitignore
vendored
4
examples/ssg-router/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
dist
|
||||
target
|
||||
static
|
||||
.dioxus
|
|
@ -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"]
|
|
@ -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!" }
|
||||
}
|
||||
}
|
||||
}
|
4
examples/ssg-simple/.gitignore
vendored
4
examples/ssg-simple/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
dist
|
||||
target
|
||||
static
|
||||
.dioxus
|
|
@ -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"]
|
|
@ -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!" }
|
||||
}
|
||||
}
|
|
@ -16,6 +16,11 @@ pub const OUT_DIR: &str = "DIOXUS_OUT_DIR";
|
|||
///
|
||||
/// This is not a websocket! There's no protocol!
|
||||
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)
|
||||
.map(|s| s.parse().ok())
|
||||
.ok()
|
||||
|
|
|
@ -21,6 +21,7 @@ dioxus-core-types = { workspace = true }
|
|||
dioxus-devtools-types = { workspace = true }
|
||||
dioxus-cli-config = { workspace = true }
|
||||
dioxus-fullstack = { workspace = true }
|
||||
dioxus-dx-wire-format = { workspace = true }
|
||||
|
||||
clap = { workspace = true, features = ["derive", "cargo"] }
|
||||
thiserror = { workspace = true }
|
||||
|
@ -30,12 +31,11 @@ uuid = { version = "1.3.0", features = ["v4"] }
|
|||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
fs_extra = "1.2.0"
|
||||
cargo_toml = { workspace = true }
|
||||
futures-util = { workspace = true, features = ["async-await-macro"] }
|
||||
notify = { workspace = true, features = ["serde"] }
|
||||
html_parser = { workspace = true }
|
||||
cargo_metadata = "0.18.1"
|
||||
cargo_metadata = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-stream = "0.1.15"
|
||||
chrono = "0.4.19"
|
||||
|
@ -71,9 +71,6 @@ reqwest = { workspace = true, features = [
|
|||
"trust-dns",
|
||||
"blocking",
|
||||
] }
|
||||
flate2 = "1.0.22"
|
||||
tar = "0.4.38"
|
||||
zip = "0.6.2"
|
||||
tower = { workspace = true }
|
||||
once_cell = "1.19.0"
|
||||
|
||||
|
@ -91,7 +88,7 @@ brotli = "6.0.0"
|
|||
ignore = "0.4.22"
|
||||
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 }
|
||||
tracing = { workspace = 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-bundler = { workspace = true }
|
||||
include_dir = "0.7.4"
|
||||
|
||||
[build-dependencies]
|
||||
built = { version = "=0.7.4", features = ["git2"] }
|
||||
|
|
1
packages/cli/assets/android/.gitignore
vendored
1
packages/cli/assets/android/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
app-dir/
|
3
packages/cli/assets/android/MainActivity.kt
Normal file
3
packages/cli/assets/android/MainActivity.kt
Normal file
|
@ -0,0 +1,3 @@
|
|||
package com.example.androidfinal
|
||||
|
||||
class MainActivity : WryActivity()
|
15
packages/cli/assets/android/gen/.gitignore
vendored
Normal file
15
packages/cli/assets/android/gen/.gitignore
vendored
Normal 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
|
49
packages/cli/assets/android/gen/app/build.gradle.kts
Normal file
49
packages/cli/assets/android/gen/app/build.gradle.kts
Normal 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")
|
||||
}
|
21
packages/cli/assets/android/gen/app/proguard-rules.pro
vendored
Normal file
21
packages/cli/assets/android/gen/app/proguard-rules.pro
vendored
Normal 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
|
|
@ -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>
|
0
packages/cli/assets/android/gen/app/src/main/assets/.gitignore
vendored
Normal file
0
packages/cli/assets/android/gen/app/src/main/assets/.gitignore
vendored
Normal file
0
packages/cli/assets/android/gen/app/src/main/kotlin/.gitignore
vendored
Normal file
0
packages/cli/assets/android/gen/app/src/main/kotlin/.gitignore
vendored
Normal 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>
|
|
@ -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>
|
|
@ -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 |
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Androidfinal</string>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
22
packages/cli/assets/android/gen/build.gradle.kts
Normal file
22
packages/cli/assets/android/gen/build.gradle.kts
Normal 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")
|
||||
}
|
||||
|
25
packages/cli/assets/android/gen/gradle.properties
Normal file
25
packages/cli/assets/android/gen/gradle.properties
Normal 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
|
BIN
packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar
vendored
Executable file
BIN
packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.jar
vendored
Executable file
Binary file not shown.
6
packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
6
packages/cli/assets/android/gen/gradle/wrapper/gradle-wrapper.properties
vendored
Executable 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
185
packages/cli/assets/android/gen/gradlew
vendored
Executable 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" "$@"
|
90
packages/cli/assets/android/gen/gradlew.bat
vendored
Normal file
90
packages/cli/assets/android/gen/gradlew.bat
vendored
Normal 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
|
2
packages/cli/assets/android/gen/settings.gradle
Normal file
2
packages/cli/assets/android/gen/settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
include ':app'
|
||||
|
|
@ -3,14 +3,18 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.dioxuslabs</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>DioxusApp</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>DioxusApp</string>
|
||||
<string>{{ display_name }}</string>
|
||||
|
||||
<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>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
|
@ -3,23 +3,23 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>DioxusApp</string>
|
||||
<string>{{ display_name }}</string>
|
||||
|
||||
<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>
|
||||
<string>icon.icns</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.dioxuslabs.dioxus-desktop</string>
|
||||
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, Platform, ProgressRx,
|
||||
ProgressTx, Result,
|
||||
AppBundle, BuildArgs, BuildRequest, BuildStage, BuildUpdate, DioxusCrate, ProgressRx,
|
||||
ProgressTx, Result, StructuredOutput,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
|
@ -40,6 +40,7 @@ impl Builder {
|
|||
pub(crate) fn start(krate: &DioxusCrate, args: BuildArgs) -> Result<Self> {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
let request = BuildRequest::new(krate.clone(), args, tx.clone());
|
||||
|
||||
Ok(Self {
|
||||
krate: krate.clone(),
|
||||
request: request.clone(),
|
||||
|
@ -49,14 +50,7 @@ impl Builder {
|
|||
// We wont bother verifying on subsequent builds
|
||||
request.verify_tooling().await?;
|
||||
|
||||
let res = request.build_all().await;
|
||||
|
||||
// The first launch gets some extra logging :)
|
||||
if res.is_ok() {
|
||||
tracing::info!("Build completed successfully, launching app! 💫")
|
||||
}
|
||||
|
||||
res
|
||||
request.build_all().await
|
||||
}),
|
||||
tx,
|
||||
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
|
||||
match &update {
|
||||
BuildUpdate::Progress { stage } => {
|
||||
// Prevent updates from flowing in after the build has already finished
|
||||
if !self.is_finished() {
|
||||
self.stage = stage.clone();
|
||||
}
|
||||
|
||||
match stage {
|
||||
BuildStage::Initializing => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 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;
|
||||
match stage {
|
||||
BuildStage::Initializing => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
}
|
||||
BuildStage::InstallingTooling {} => {}
|
||||
BuildStage::Compiling {
|
||||
current,
|
||||
total,
|
||||
platform,
|
||||
..
|
||||
} => {
|
||||
if *platform == Platform::Server {
|
||||
self.compiled_crates_server = *current;
|
||||
self.expected_crates_server = *total;
|
||||
} else {
|
||||
self.compiled_crates = *current;
|
||||
self.expected_crates = *total;
|
||||
BuildStage::Starting {
|
||||
crate_count,
|
||||
is_server,
|
||||
} => {
|
||||
if *is_server {
|
||||
self.expected_crates_server = *crate_count;
|
||||
} else {
|
||||
self.expected_crates = *crate_count;
|
||||
}
|
||||
}
|
||||
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() {
|
||||
self.compile_start = Some(Instant::now());
|
||||
if self.compile_start.is_none() {
|
||||
self.compile_start = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
BuildStage::Bundling {} => {
|
||||
self.complete_compile();
|
||||
self.bundling_progress = 0.0;
|
||||
self.bundle_start = Some(Instant::now());
|
||||
}
|
||||
BuildStage::OptimizingWasm {} => {}
|
||||
BuildStage::CopyingAssets { current, total, .. } => {
|
||||
self.bundling_progress = *current as f64 / *total as f64;
|
||||
}
|
||||
BuildStage::Success => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Failed => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Aborted => {}
|
||||
BuildStage::Restarting => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.expected_crates = 1;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
BuildStage::RunningBindgen {} => {
|
||||
self.bundling_progress = 0.5;
|
||||
BuildStage::Bundling {} => {
|
||||
self.complete_compile();
|
||||
self.bundling_progress = 0.0;
|
||||
self.bundle_start = Some(Instant::now());
|
||||
}
|
||||
BuildStage::OptimizingWasm {} => {}
|
||||
BuildStage::CopyingAssets { current, total, .. } => {
|
||||
self.bundling_progress = *current as f64 / *total as f64;
|
||||
}
|
||||
BuildStage::Success => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Failed => {
|
||||
self.compiled_crates = self.expected_crates;
|
||||
self.compiled_crates_server = self.expected_crates_server;
|
||||
self.bundling_progress = 1.0;
|
||||
}
|
||||
BuildStage::Aborted => {}
|
||||
BuildStage::Restarting => {
|
||||
self.compiled_crates = 0;
|
||||
self.compiled_crates_server = 0;
|
||||
self.expected_crates = 1;
|
||||
self.bundling_progress = 0.0;
|
||||
}
|
||||
BuildStage::RunningBindgen {} => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -224,10 +215,43 @@ impl Builder {
|
|||
pub(crate) async fn finish(&mut self) -> Result<AppBundle> {
|
||||
loop {
|
||||
match self.wait().await {
|
||||
BuildUpdate::BuildReady { bundle } => return Ok(bundle),
|
||||
BuildUpdate::BuildFailed { err } => return Err(err),
|
||||
BuildUpdate::Progress { .. } => {}
|
||||
BuildUpdate::CompilerMessage { .. } => {}
|
||||
BuildUpdate::Progress { stage } => {
|
||||
match &stage {
|
||||
BuildStage::Compiling {
|
||||
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
|
||||
}
|
||||
|
||||
fn is_finished(&self) -> bool {
|
||||
pub(crate) fn is_finished(&self) -> bool {
|
||||
match self.stage {
|
||||
BuildStage::Success => true,
|
||||
BuildStage::Failed => true,
|
|
@ -3,13 +3,16 @@ use crate::{assets::AssetManifest, TraceSrc};
|
|||
use crate::{BuildRequest, Platform};
|
||||
use anyhow::Context;
|
||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::{
|
||||
fs::create_dir_all,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{sync::atomic::AtomicUsize, time::Duration};
|
||||
use tokio::process::Command;
|
||||
use wasm_bindgen_cli_support::Bindgen;
|
||||
|
||||
use super::templates::InfoPlistData;
|
||||
|
||||
/// The end result of a build.
|
||||
///
|
||||
/// Contains the final asset manifest, the executables, and the workdir.
|
||||
|
@ -93,13 +96,14 @@ pub(crate) struct AppBundle {
|
|||
pub struct BuildArtifacts {
|
||||
pub(crate) exe: PathBuf,
|
||||
pub(crate) assets: AssetManifest,
|
||||
pub(crate) time_taken: Duration,
|
||||
}
|
||||
|
||||
impl AppBundle {
|
||||
/// ## Web:
|
||||
/// Create a folder that is somewhat similar to an app-image (exe + asset)
|
||||
/// The server is dropped into the `web` folder, even if there's no `public` folder.
|
||||
/// If there's no server (SPA/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.
|
||||
/// ```
|
||||
/// web/
|
||||
|
@ -255,52 +259,40 @@ impl AppBundle {
|
|||
/// root().join(bundled)
|
||||
/// ```
|
||||
pub(crate) async fn new(
|
||||
request: BuildRequest,
|
||||
build: BuildRequest,
|
||||
app: BuildArtifacts,
|
||||
server: Option<BuildArtifacts>,
|
||||
) -> Result<Self> {
|
||||
let bundle = Self {
|
||||
app,
|
||||
server,
|
||||
build: request,
|
||||
};
|
||||
let bundle = Self { app, server, build };
|
||||
|
||||
tracing::debug!("Assembling app 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_assets().await?;
|
||||
bundle
|
||||
.write_assets()
|
||||
.await
|
||||
.context("Failed to write assets")?;
|
||||
bundle.write_metadata().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)
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// 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 => {
|
||||
// Run wasm-bindgen and drop its output into the assets folder under "dioxus"
|
||||
self.build.status_wasm_bindgen_start();
|
||||
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.exe_dir())
|
||||
self.run_wasm_bindgen(&self.app.exe.with_extension("wasm"), &self.build.exe_dir())
|
||||
.await?;
|
||||
|
||||
// Only run wasm-opt if the feature is enabled
|
||||
// Wasm-opt has an expensive build script that makes it annoying to keep enabled for iterative dev
|
||||
// We put it behind the "wasm-opt" feature flag so that it can be disabled when iterating on the cli
|
||||
self.build.status_wasm_opt_start();
|
||||
self.run_wasm_opt(&self.exe_dir())?;
|
||||
self.run_wasm_opt(&self.build.exe_dir())?;
|
||||
|
||||
// Write the index.html file with the pre-configured contents we got from pre-rendering
|
||||
std::fs::write(
|
||||
self.app_dir().join("index.html"),
|
||||
self.build.root_dir().join("index.html"),
|
||||
self.build.prepare_html()?,
|
||||
)?;
|
||||
}
|
||||
|
@ -385,7 +376,7 @@ impl AppBundle {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let asset_dir = self.asset_dir();
|
||||
let asset_dir = self.build.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
|
||||
|
@ -418,10 +409,9 @@ impl AppBundle {
|
|||
// 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
|
||||
assets_to_transfer.par_iter().try_for_each(|(from, to)| {
|
||||
self.build.status_copying_asset(
|
||||
assets_finished.fetch_add(0, std::sync::atomic::Ordering::SeqCst),
|
||||
asset_count,
|
||||
from.clone(),
|
||||
tracing::trace!(
|
||||
"Starting asset copy {current}/{asset_count} from {from:?}",
|
||||
current = assets_finished.fetch_add(0, std::sync::atomic::Ordering::SeqCst),
|
||||
);
|
||||
|
||||
// todo(jon): implement optimize + pre_compress on the asset type
|
||||
|
@ -431,7 +421,7 @@ impl AppBundle {
|
|||
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,
|
||||
asset_count,
|
||||
from.clone(),
|
||||
|
@ -443,66 +433,11 @@ impl AppBundle {
|
|||
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.
|
||||
///
|
||||
/// 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 {
|
||||
// todo(jon): this could just be named `App` or the name of the app like `Raycast` in `Raycast.app`
|
||||
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"),
|
||||
}
|
||||
self.build.exe_dir().join(self.build.platform_exe_name())
|
||||
}
|
||||
|
||||
/// We always put the server in the `web` folder!
|
||||
|
@ -531,15 +466,15 @@ impl AppBundle {
|
|||
// write the Info.plist file
|
||||
match self.build.build.platform() {
|
||||
Platform::MacOS => {
|
||||
let src = include_str!("../../assets/macos/mac.plist");
|
||||
let dest = self.app_dir().join("Contents").join("Info.plist");
|
||||
std::fs::write(dest, src)?;
|
||||
let dest = self.build.root_dir().join("Contents").join("Info.plist");
|
||||
let plist = self.macos_plist_contents()?;
|
||||
std::fs::write(dest, plist)?;
|
||||
}
|
||||
|
||||
Platform::Ios => {
|
||||
let src = include_str!("../../assets/ios/ios.plist");
|
||||
let dest = self.app_dir().join("Info.plist");
|
||||
std::fs::write(dest, src)?;
|
||||
let dest = self.build.root_dir().join("Info.plist");
|
||||
let plist = self.ios_plist_contents()?;
|
||||
std::fs::write(dest, plist)?;
|
||||
}
|
||||
|
||||
// AndroidManifest.xml
|
||||
|
@ -575,7 +510,7 @@ impl AppBundle {
|
|||
.krate
|
||||
.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 || {
|
||||
crate::fastfs::pre_compress_folder(&bindgen_dir, pre_compress)
|
||||
})
|
||||
|
@ -618,37 +553,6 @@ impl AppBundle {
|
|||
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(
|
||||
&self,
|
||||
input_path: &Path,
|
||||
|
@ -691,7 +595,6 @@ impl AppBundle {
|
|||
if !self.build.build.release {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.build.status_optimizing_wasm();
|
||||
|
||||
#[cfg(feature = "optimizations")]
|
||||
|
@ -799,4 +702,76 @@ impl AppBundle {
|
|||
|
||||
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")
|
||||
}
|
||||
}
|
|
@ -5,14 +5,15 @@
|
|||
//! Uses a request -> response architecture that allows you to monitor the progress with an optional message
|
||||
//! receiver.
|
||||
|
||||
mod build;
|
||||
mod builder;
|
||||
mod bundle;
|
||||
mod progress;
|
||||
mod runner;
|
||||
mod request;
|
||||
mod templates;
|
||||
mod verify;
|
||||
mod web;
|
||||
|
||||
pub(crate) use build::*;
|
||||
pub(crate) use builder::*;
|
||||
pub(crate) use bundle::*;
|
||||
pub(crate) use progress::*;
|
||||
pub(crate) use runner::*;
|
||||
pub(crate) use request::*;
|
|
@ -1,5 +1,5 @@
|
|||
//! 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 futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use std::path::PathBuf;
|
||||
|
@ -16,46 +16,12 @@ pub(crate) enum BuildUpdate {
|
|||
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 {
|
||||
pub(crate) fn status_wasm_bindgen_start(&self) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
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) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
|
@ -73,19 +39,13 @@ impl BuildRequest {
|
|||
tracing::trace!(dx_src = ?TraceSrc::Cargo, "{line}");
|
||||
}
|
||||
|
||||
pub(crate) fn status_build_progress(
|
||||
&self,
|
||||
count: usize,
|
||||
total: usize,
|
||||
name: String,
|
||||
platform: Platform,
|
||||
) {
|
||||
pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::Compiling {
|
||||
current: count,
|
||||
total,
|
||||
krate: name,
|
||||
platform,
|
||||
is_server: self.is_server(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -93,14 +53,13 @@ impl BuildRequest {
|
|||
pub(crate) fn status_starting_build(&self, crate_count: usize) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::Starting {
|
||||
platform: self.build.platform(),
|
||||
is_server: self.build.platform() == Platform::Server,
|
||||
crate_count,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn status_copying_asset(&self, current: usize, total: usize, path: PathBuf) {
|
||||
tracing::trace!("Status copying asset {current}/{total} from {path:?}");
|
||||
pub(crate) fn status_copied_asset(&self, current: usize, total: usize, path: PathBuf) {
|
||||
_ = self.progress.unbounded_send(BuildUpdate::Progress {
|
||||
stage: BuildStage::CopyingAssets {
|
||||
current,
|
||||
|
@ -121,4 +80,8 @@ impl BuildRequest {
|
|||
stage: BuildStage::InstallingTooling {},
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn is_server(&self) -> bool {
|
||||
self.build.platform() == Platform::Server
|
||||
}
|
||||
}
|
|
@ -2,14 +2,11 @@ use super::{progress::ProgressTx, BuildArtifacts};
|
|||
use crate::dioxus_crate::DioxusCrate;
|
||||
use crate::Result;
|
||||
use crate::{assets::AssetManifest, TraceSrc};
|
||||
use crate::{build::BuildArgs, link::LinkAction};
|
||||
use crate::{link::LinkAction, BuildArgs};
|
||||
use crate::{AppBundle, Platform};
|
||||
use anyhow::Context;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
};
|
||||
use std::{path::PathBuf, process::Stdio, time::Instant};
|
||||
use tokio::{io::AsyncBufReadExt, process::Command};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -44,17 +41,32 @@ impl BuildRequest {
|
|||
pub(crate) async fn build_all(self) -> Result<AppBundle> {
|
||||
tracing::debug!("Running build command...");
|
||||
|
||||
let (app, server) =
|
||||
futures_util::future::try_join(self.build_app(), self.build_server()).await?;
|
||||
let (app, server) = self.build_concurrent().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> {
|
||||
tracing::debug!("Building app...");
|
||||
|
||||
let start = Instant::now();
|
||||
self.prepare_build_dir()?;
|
||||
let exe = self.build_cargo().await?;
|
||||
let assets = self.collect_assets(&exe).await?;
|
||||
Ok(BuildArtifacts { exe, assets })
|
||||
let assets = self.collect_assets().await?;
|
||||
|
||||
Ok(BuildArtifacts {
|
||||
exe,
|
||||
assets,
|
||||
time_taken: start.elapsed(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn build_server(&self) -> Result<Option<BuildArtifacts>> {
|
||||
|
@ -87,8 +99,9 @@ impl BuildRequest {
|
|||
.current_dir(self.krate.crate_dir())
|
||||
.arg("--message-format")
|
||||
.arg("json-diagnostic-rendered-ansi")
|
||||
.args(self.build_arguments());
|
||||
// .env("RUSTFLAGS", self.rust_flags());
|
||||
.args(self.build_arguments())
|
||||
.envs(self.env_vars());
|
||||
// .args(["--", "-Csave-temps=y"]);
|
||||
|
||||
if let Some(target_dir) = self.custom_target_dir.as_ref() {
|
||||
cmd.env("CARGO_TARGET_DIR", target_dir);
|
||||
|
@ -153,7 +166,6 @@ impl BuildRequest {
|
|||
units_compiled,
|
||||
crate_count,
|
||||
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
|
||||
/// 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.
|
||||
pub(crate) async fn collect_assets(&self, exe: &Path) -> Result<AssetManifest> {
|
||||
pub(crate) async fn collect_assets(&self) -> Result<AssetManifest> {
|
||||
tracing::debug!("Collecting assets ...");
|
||||
|
||||
// If assets are skipped, we don't need to collect them
|
||||
if self.build.skip_assets {
|
||||
return Ok(AssetManifest::default());
|
||||
}
|
||||
|
||||
let mut manifest = AssetManifest::default();
|
||||
|
||||
_ = manifest.add_from_object_path(exe.to_path_buf());
|
||||
|
||||
Ok(manifest)
|
||||
self.deep_linker_asset_extract().await
|
||||
}
|
||||
|
||||
/// Create a list of arguments for cargo builds
|
||||
|
@ -254,11 +261,8 @@ impl BuildRequest {
|
|||
}
|
||||
}
|
||||
|
||||
if self.build.verbose {
|
||||
cargo_args.push("--verbose".to_string());
|
||||
} else {
|
||||
cargo_args.push("--quiet".to_string());
|
||||
}
|
||||
// We always run in verbose since the CLI itself is the one doing the presentation
|
||||
cargo_args.push("--verbose".to_string());
|
||||
|
||||
if self.build.target_args.no_default_features {
|
||||
cargo_args.push("--no-default-features".to_string());
|
||||
|
@ -293,7 +297,7 @@ impl BuildRequest {
|
|||
}
|
||||
|
||||
#[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();
|
||||
|
||||
if self.build.platform() == Platform::Android {
|
||||
|
@ -355,6 +359,7 @@ impl BuildRequest {
|
|||
.arg("-Z")
|
||||
.arg("unstable-options")
|
||||
.args(self.build_arguments())
|
||||
.envs(self.env_vars())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.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
|
||||
/// need to revert to "deep extraction".
|
||||
#[allow(unused)]
|
||||
async fn deep_linker_asset_extract(&self) -> Result<AssetManifest> {
|
||||
// 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
|
||||
|
@ -406,9 +410,9 @@ impl BuildRequest {
|
|||
//
|
||||
// This will force `dx` to look through the incremental cache and find the assets from the previous build
|
||||
Command::new("cargo")
|
||||
// .env("RUSTFLAGS", self.rust_flags())
|
||||
.arg("rustc")
|
||||
.args(self.build_arguments())
|
||||
.envs(self.env_vars())
|
||||
.arg("--offline") /* don't use the network, should already be resolved */
|
||||
.arg("--")
|
||||
.arg(format!(
|
||||
|
@ -422,7 +426,7 @@ impl BuildRequest {
|
|||
.env(
|
||||
LinkAction::ENV_VAR_NAME,
|
||||
LinkAction::BuildAssetManifest {
|
||||
destination: tmp_file.path().to_path_buf(),
|
||||
destination: tmp_file.path().to_path_buf().clone(),
|
||||
}
|
||||
.to_json(),
|
||||
)
|
||||
|
@ -432,6 +436,351 @@ impl BuildRequest {
|
|||
.await?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
}
|
7
packages/cli/src/build/templates.rs
Normal file
7
packages/cli/src/build/templates.rs
Normal 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,
|
||||
}
|
|
@ -94,21 +94,21 @@ impl BuildRequest {
|
|||
/// should be installing the x86 versions.
|
||||
pub(crate) async fn verify_ios_tooling(&self, _rustup: RustupShow) -> Result<()> {
|
||||
// open the simulator
|
||||
_ = tokio::process::Command::new("open")
|
||||
.arg("/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app")
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.status()
|
||||
.await;
|
||||
// _ = tokio::process::Command::new("open")
|
||||
// .arg("/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app")
|
||||
// .stderr(Stdio::piped())
|
||||
// .stdout(Stdio::piped())
|
||||
// .status()
|
||||
// .await;
|
||||
|
||||
// Now xcrun to open the device
|
||||
// todo: we should try and query the device list and/or parse it rather than hardcode this simulator
|
||||
_ = tokio::process::Command::new("xcrun")
|
||||
.args(["simctl", "boot", "83AE3067-987F-4F85-AE3D-7079EF48C967"])
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.status()
|
||||
.await;
|
||||
// _ = tokio::process::Command::new("xcrun")
|
||||
// .args(["simctl", "boot", "83AE3067-987F-4F85-AE3D-7079EF48C967"])
|
||||
// .stderr(Stdio::piped())
|
||||
// .stdout(Stdio::piped())
|
||||
// .status()
|
||||
// .await;
|
||||
|
||||
// if !rustup
|
||||
// .installed_toolchains
|
|
@ -3,12 +3,6 @@ use crate::{
|
|||
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 {
|
||||
fn from(val: NsisSettings) -> Self {
|
||||
tauri_bundler::NsisSettings {
|
||||
|
@ -162,6 +156,7 @@ impl From<PackageType> for tauri_bundler::PackageType {
|
|||
PackageType::AppImage => Self::AppImage,
|
||||
PackageType::Dmg => Self::Dmg,
|
||||
PackageType::Updater => Self::Updater,
|
||||
PackageType::Nsis => Self::Nsis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use super::*;
|
||||
use crate::DioxusCrate;
|
||||
use anyhow::Context;
|
||||
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||
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
|
||||
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
|
||||
|
@ -37,7 +38,7 @@ pub(crate) struct Autoformat {
|
|||
}
|
||||
|
||||
impl Autoformat {
|
||||
pub(crate) fn autoformat(self) -> Result<()> {
|
||||
pub(crate) fn autoformat(self) -> Result<StructuredOutput> {
|
||||
let Autoformat {
|
||||
check,
|
||||
raw,
|
||||
|
@ -56,9 +57,7 @@ impl Autoformat {
|
|||
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
|
||||
println!("{}", inner);
|
||||
} else {
|
||||
// exit process with error
|
||||
eprintln!("error formatting codeblock");
|
||||
exit(1);
|
||||
return Err("error formatting codeblock".into());
|
||||
}
|
||||
} else {
|
||||
// Default to formatting the project.
|
||||
|
@ -68,13 +67,9 @@ impl Autoformat {
|
|||
package: Some(package),
|
||||
..Default::default()
|
||||
};
|
||||
let dx_crate = match DioxusCrate::new(&target_args) {
|
||||
Ok(x) => x,
|
||||
Err(error) => {
|
||||
eprintln!("failed to parse crate graph: {error}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let dx_crate =
|
||||
DioxusCrate::new(&target_args).context("failed to parse crate graph")?;
|
||||
|
||||
Cow::Owned(dx_crate.crate_dir())
|
||||
} else {
|
||||
Cow::Borrowed(Path::new("."))
|
||||
|
@ -83,12 +78,11 @@ impl Autoformat {
|
|||
if let Err(e) =
|
||||
autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
|
||||
{
|
||||
eprintln!("error formatting project: {}", e);
|
||||
exit(1);
|
||||
return Err(format!("error formatting project: {}", e).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,8 +100,7 @@ fn refactor_file(
|
|||
fs::read_to_string(&file)
|
||||
};
|
||||
let Ok(mut s) = file_content else {
|
||||
eprintln!("failed to open file: {}", file_content.unwrap_err());
|
||||
exit(1);
|
||||
return Err(format!("failed to open file: {}", file_content.unwrap_err()).into());
|
||||
};
|
||||
|
||||
if format_rust_code {
|
||||
|
@ -117,8 +110,7 @@ fn refactor_file(
|
|||
let Ok(Ok(edits)) =
|
||||
syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent))
|
||||
else {
|
||||
eprintln!("failed to format file: {}", s);
|
||||
exit(1);
|
||||
return Err(format!("failed to format file: {}", s).into());
|
||||
};
|
||||
|
||||
let out = dioxus_autofmt::apply_formats(&s, edits);
|
||||
|
@ -126,7 +118,7 @@ fn refactor_file(
|
|||
if file == "-" {
|
||||
print!("{}", 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 {
|
||||
println!("formatted {}", file);
|
||||
}
|
||||
|
@ -212,7 +204,7 @@ fn autoformat_project(
|
|||
match res {
|
||||
Ok(cnt) => Some(cnt),
|
||||
Err(err) => {
|
||||
eprintln!("error formatting file : {}\n{:#?}", path.display(), err);
|
||||
tracing::error!("error formatting file : {}\n{:#?}", path.display(), err);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -222,8 +214,7 @@ fn autoformat_project(
|
|||
let files_formatted: usize = counts.into_iter().flatten().sum();
|
||||
|
||||
if files_formatted > 0 && check {
|
||||
eprintln!("{} files needed formatting", files_formatted);
|
||||
exit(1);
|
||||
return Err(format!("{} files needed formatting", files_formatted).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
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.
|
||||
///
|
||||
/// Produces a final output bundle designed to be run on the target platform.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[clap(name = "build")]
|
||||
pub(crate) struct BuildArgs {
|
||||
/// Build in release mode [default: false]
|
||||
#[clap(long, short)]
|
||||
|
@ -17,21 +16,6 @@ pub(crate) struct BuildArgs {
|
|||
#[serde(default)]
|
||||
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
|
||||
#[clap(long)]
|
||||
pub(crate) profile: Option<String>,
|
||||
|
@ -73,12 +57,9 @@ pub(crate) struct BuildArgs {
|
|||
}
|
||||
|
||||
impl BuildArgs {
|
||||
pub async fn build_it(&mut self) -> Result<()> {
|
||||
self.build().await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn run_cmd(mut self) -> Result<StructuredOutput> {
|
||||
tracing::info!("Building project...");
|
||||
|
||||
pub(crate) async fn build(&mut self) -> Result<AppBundle> {
|
||||
let krate =
|
||||
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?;
|
||||
|
||||
println!(
|
||||
"Successfully built! 💫\nBundle at {}",
|
||||
bundle.app_dir().display()
|
||||
);
|
||||
tracing::info!(path = ?bundle.build.root_dir(), "Build completed successfully! 🚀");
|
||||
|
||||
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
|
||||
|
@ -127,13 +107,13 @@ impl BuildArgs {
|
|||
// Add any features required to turn on the client
|
||||
self.target_args
|
||||
.client_features
|
||||
.extend(krate.feature_for_platform(platform));
|
||||
.push(krate.feature_for_platform(platform));
|
||||
|
||||
// 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
|
||||
self.target_args
|
||||
.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
|
||||
// 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
|
||||
// We do this for android/wasm since they require
|
||||
if self.profile.is_none() {
|
||||
if self.profile.is_none() && !self.release {
|
||||
match self.platform {
|
||||
Some(Platform::Android) => {
|
||||
self.profile = Some(crate::dioxus_crate::PROFILE_ANDROID.to_string());
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
use crate::bundle_utils::make_tauri_bundler_settings;
|
||||
use crate::DioxusCrate;
|
||||
use crate::{build::BuildArgs, PackageType};
|
||||
use crate::{AppBundle, BuildArgs, Builder, DioxusCrate, Platform};
|
||||
use anyhow::Context;
|
||||
use std::env::current_dir;
|
||||
use std::str::FromStr;
|
||||
use tauri_bundler::{PackageSettings, SettingsBuilder};
|
||||
use std::collections::HashMap;
|
||||
use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Bundle the Rust desktop app and all of its assets
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(name = "bundle")]
|
||||
pub struct 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)]
|
||||
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
|
||||
#[clap(flatten)]
|
||||
|
@ -22,31 +38,124 @@ pub struct 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)
|
||||
.context("Failed to load Dioxus workspace")?;
|
||||
|
||||
// We always use `release` mode for bundling
|
||||
self.build_arguments.release = true;
|
||||
self.build_arguments.resolve(&krate)?;
|
||||
|
||||
// Build the app
|
||||
let bundle = self.build_arguments.build().await?;
|
||||
tracing::info!("Building app...");
|
||||
|
||||
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 mut name: PathBuf = krate.executable_name().into();
|
||||
if cfg!(windows) {
|
||||
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![
|
||||
tauri_bundler::BundleBinary::new(name.display().to_string(), true)
|
||||
.set_src_path(Some(krate.workspace_dir().display().to_string())),
|
||||
// We use the name of the exe but it has to be in the same directory
|
||||
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 = make_tauri_bundler_settings(bundle_config);
|
||||
let mut bundle_settings: BundleSettings = krate.config.bundle.clone().into();
|
||||
|
||||
if cfg!(windows) {
|
||||
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
|
||||
let ignored_files = [krate
|
||||
.bundle_dir(self.build_arguments.platform())
|
||||
.join("bundle")];
|
||||
|
||||
for entry in std::fs::read_dir(bundle.asset_dir())?.flatten() {
|
||||
let path = entry.path().canonicalize()?;
|
||||
if ignored_files.iter().any(|f| path.starts_with(f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tauri bundle will add a __root__ prefix if the input path is absolute even though the output path is relative?
|
||||
// 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
|
||||
let path = path
|
||||
.strip_prefix(¤t_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());
|
||||
}
|
||||
if bundle_settings.resources_map.is_none() {
|
||||
bundle_settings.resources_map = Some(HashMap::new());
|
||||
}
|
||||
|
||||
for entry in std::fs::read_dir(bundle.build.asset_dir())?.flatten() {
|
||||
let old = entry.path().canonicalize()?;
|
||||
let new = PathBuf::from("assets").join(old.file_name().unwrap());
|
||||
tracing::debug!("Bundled asset: {old:?} -> {new:?}");
|
||||
|
||||
bundle_settings
|
||||
.resources_map
|
||||
.as_mut()
|
||||
.expect("to be set")
|
||||
.insert(old.display().to_string(), new.display().to_string());
|
||||
}
|
||||
|
||||
// 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() {
|
||||
if let Some(resources) = &mut bundle_settings.resources_map {
|
||||
resources.insert(resource_path, "".to_string());
|
||||
} else {
|
||||
bundle_settings.resources_map = Some([(resource_path, "".to_string())].into());
|
||||
}
|
||||
bundle_settings
|
||||
.resources_map
|
||||
.as_mut()
|
||||
.expect("to be set")
|
||||
.insert(resource_path, "".to_string());
|
||||
}
|
||||
|
||||
let mut settings = SettingsBuilder::new()
|
||||
|
@ -106,9 +205,11 @@ impl Bundle {
|
|||
authors: Some(package.authors.clone()),
|
||||
default_run: Some(krate.executable_name().to_string()),
|
||||
})
|
||||
.log_level(log::Level::Debug)
|
||||
.binaries(binaries)
|
||||
.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());
|
||||
}
|
||||
|
||||
|
@ -116,37 +217,23 @@ impl Bundle {
|
|||
settings = settings.target(target.to_string());
|
||||
}
|
||||
|
||||
let settings = settings.build();
|
||||
|
||||
// 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)),
|
||||
if self.build_arguments.platform() == Platform::Ios {
|
||||
settings = settings.target("aarch64-apple-ios".to_string());
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 crate::DioxusCrate;
|
||||
use anyhow::Context;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use std::{path::Path, process::exit};
|
||||
|
||||
// For reference, the rustfmt main.rs file
|
||||
// https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
|
||||
use std::path::Path;
|
||||
|
||||
/// Check the Rust files in the project for issues.
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
|
@ -20,25 +23,23 @@ pub(crate) struct Check {
|
|||
|
||||
impl Check {
|
||||
// Todo: check the entire crate
|
||||
pub(crate) async fn check(self) -> Result<()> {
|
||||
pub(crate) async fn check(self) -> Result<StructuredOutput> {
|
||||
match self.file {
|
||||
// Default to checking the project
|
||||
None => {
|
||||
let dioxus_crate = DioxusCrate::new(&self.target_args)?;
|
||||
if let Err(e) = check_project_and_report(dioxus_crate).await {
|
||||
eprintln!("error checking project: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
check_project_and_report(dioxus_crate)
|
||||
.await
|
||||
.context("error checking project")?;
|
||||
}
|
||||
Some(file) => {
|
||||
if let Err(e) = check_file_and_report(file).await {
|
||||
eprintln!("failed to check file: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
check_file_and_report(file)
|
||||
.await
|
||||
.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;
|
||||
|
||||
if res.is_err() {
|
||||
eprintln!("error checking file: {}", path.display());
|
||||
tracing::error!("error checking file: {}", path.display());
|
||||
}
|
||||
|
||||
res
|
||||
|
@ -92,19 +93,17 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
|
|||
|
||||
for report in issue_reports.into_iter() {
|
||||
if !report.issues.is_empty() {
|
||||
println!("{}", report);
|
||||
tracing::info!("{}", report);
|
||||
}
|
||||
}
|
||||
|
||||
match total_issues {
|
||||
0 => println!("No issues found."),
|
||||
1 => println!("1 issue found."),
|
||||
_ => println!("{} issues found.", total_issues),
|
||||
}
|
||||
|
||||
match total_issues {
|
||||
0 => exit(0),
|
||||
_ => exit(1),
|
||||
0 => {
|
||||
tracing::info!("No issues found.");
|
||||
Ok(())
|
||||
}
|
||||
1 => Err("1 issue found.".into()),
|
||||
_ => Err(format!("{} issues found.", total_issues).into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +113,6 @@ fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
|
|||
};
|
||||
|
||||
// load the gitignore
|
||||
|
||||
for entry in folder {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
use super::*;
|
||||
use crate::Result;
|
||||
|
||||
/// Clean build artifacts.
|
||||
///
|
||||
/// Simlpy runs `cargo clean`
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(name = "clean")]
|
||||
pub(crate) struct 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
|
||||
pub(crate) fn clean(self) -> anyhow::Result<()> {
|
||||
let output = Command::new("cargo")
|
||||
pub(crate) async fn clean(self) -> Result<StructuredOutput> {
|
||||
let output = tokio::process::Command::new("cargo")
|
||||
.arg("clean")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output()?;
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Cargo clean failed."));
|
||||
return Err(anyhow::anyhow!("Cargo clean failed.").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::{metadata::crate_root, CliSettings};
|
|||
|
||||
/// Dioxus config file controls
|
||||
#[derive(Clone, Debug, Deserialize, Subcommand)]
|
||||
#[clap(name = "config")]
|
||||
pub(crate) enum Config {
|
||||
/// Init `Dioxus.toml` for project/folder.
|
||||
Init {
|
||||
|
@ -76,7 +75,7 @@ impl From<BoolValue> for bool {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn config(self) -> Result<()> {
|
||||
pub(crate) fn config(self) -> Result<StructuredOutput> {
|
||||
let crate_root = crate_root()?;
|
||||
match self {
|
||||
Config::Init {
|
||||
|
@ -89,7 +88,7 @@ impl Config {
|
|||
tracing::warn!(
|
||||
"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 content = String::from(include_str!("../../assets/dioxus.toml"))
|
||||
|
@ -99,7 +98,7 @@ impl Config {
|
|||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 Init config file completed.");
|
||||
}
|
||||
Config::FormatPrint {} => {
|
||||
println!(
|
||||
tracing::info!(
|
||||
"{:#?}",
|
||||
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.");
|
||||
}
|
||||
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());
|
||||
}
|
||||
// Handle CLI settings.
|
||||
|
@ -132,6 +131,7 @@ impl Config {
|
|||
tracing::info!(dx_src = ?TraceSrc::Dev, "🚩 CLI setting `{setting}` has been set.");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use std::path::Path;
|
|||
pub(crate) static DEFAULT_TEMPLATE: &str = "gh:dioxuslabs/dioxus-template";
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[clap(name = "new")]
|
||||
pub(crate) struct Create {
|
||||
/// Project name (required when `--yes` is used)
|
||||
name: Option<String>,
|
||||
|
@ -39,7 +38,7 @@ pub(crate) struct 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();
|
||||
|
||||
// If we're getting pass a `.` name, that's actually a path
|
||||
|
@ -106,7 +105,9 @@ impl Create {
|
|||
.expect("ctrlc::set_handler");
|
||||
let path = cargo_generate::generate(args)?;
|
||||
|
||||
post_create(&path, metadata)
|
||||
post_create(&path, metadata)?;
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::{Result, StructuredOutput};
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct Doctor {}
|
||||
|
||||
impl Doctor {
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
pub async fn run(self) -> Result<StructuredOutput> {
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,28 +3,27 @@ use crate::cli::create::DEFAULT_TEMPLATE;
|
|||
use cargo_generate::{GenerateArgs, TemplatePath};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[clap(name = "init")]
|
||||
pub(crate) struct Init {
|
||||
/// Template path
|
||||
#[clap(default_value = DEFAULT_TEMPLATE, short, long)]
|
||||
template: String,
|
||||
|
||||
/// Pass <option>=<value> for the used template (e.g., `foo=bar`)
|
||||
#[clap(short, long)]
|
||||
option: Vec<String>,
|
||||
|
||||
/// Specify a sub-template within the template repository to be used as the actual template
|
||||
#[clap(long)]
|
||||
subtemplate: Option<String>,
|
||||
|
||||
/// Skip user interaction by using the default values for the used template.
|
||||
/// Default values can be overridden with `--option`
|
||||
#[clap(short, long)]
|
||||
yes: bool,
|
||||
// TODO: turn on/off cargo-generate's output (now is invisible)
|
||||
// #[clap(default_value = "false", short, long)]
|
||||
// silent: bool,
|
||||
}
|
||||
|
||||
impl Init {
|
||||
pub(crate) fn init(self) -> Result<()> {
|
||||
pub(crate) fn init(self) -> Result<StructuredOutput> {
|
||||
let metadata = cargo_metadata::MetadataCommand::new().exec().ok();
|
||||
|
||||
// Get directory name.
|
||||
|
@ -57,6 +56,8 @@ impl Init {
|
|||
..Default::default()
|
||||
};
|
||||
let path = cargo_generate::generate(args)?;
|
||||
create::post_create(&path, metadata)
|
||||
create::post_create(&path, metadata)?;
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::assets::AssetManifest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::assets::AssetManifest;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum LinkAction {
|
||||
BuildAssetManifest {
|
||||
|
@ -15,7 +14,7 @@ pub enum 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)?
|
||||
///
|
||||
|
@ -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
|
||||
/// 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 {
|
||||
// Literally just run the android linker :)
|
||||
LinkAction::LinkAndroid {
|
||||
|
@ -102,7 +101,5 @@ impl LinkAction {
|
|||
std::fs::write(dest, contents).expect("Failed to write output file");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ pub(crate) mod run;
|
|||
pub(crate) mod serve;
|
||||
pub(crate) mod target;
|
||||
pub(crate) mod translate;
|
||||
pub(crate) mod verbosity;
|
||||
|
||||
pub(crate) use build::*;
|
||||
pub(crate) use serve::*;
|
||||
pub(crate) use target::*;
|
||||
pub(crate) use verbosity::*;
|
||||
|
||||
use crate::{error::Result, Error};
|
||||
use crate::{error::Result, Error, StructuredOutput};
|
||||
use anyhow::Context;
|
||||
use clap::{Parser, Subcommand};
|
||||
use html_parser::Dom;
|
||||
|
@ -35,40 +37,42 @@ use std::{
|
|||
#[derive(Parser)]
|
||||
#[clap(name = "dioxus", version = VERSION.as_str())]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
#[command(subcommand)]
|
||||
pub(crate) action: Commands,
|
||||
|
||||
/// Enable verbose logging.
|
||||
#[clap(short)]
|
||||
pub(crate) v: bool,
|
||||
|
||||
/// Specify a binary target.
|
||||
#[clap(global = true, long)]
|
||||
pub(crate) bin: Option<String>,
|
||||
#[command(flatten)]
|
||||
pub(crate) verbosity: Verbosity,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Build the Dioxus project and all of its assets.
|
||||
#[clap(name = "build")]
|
||||
Build(build::BuildArgs),
|
||||
|
||||
/// Translate a source file into Dioxus code.
|
||||
#[clap(name = "translate")]
|
||||
Translate(translate::Translate),
|
||||
|
||||
/// Build, watch & serve the Dioxus project and all of its assets.
|
||||
#[clap(name = "serve")]
|
||||
Serve(serve::ServeArgs),
|
||||
|
||||
/// Create a new project for Dioxus.
|
||||
#[clap(name = "new")]
|
||||
New(create::Create),
|
||||
|
||||
/// Init a new project for Dioxus in an existing directory.
|
||||
/// Will attempt to keep your project in a good state.
|
||||
#[clap(name = "init")]
|
||||
Init(init::Init),
|
||||
|
||||
/// Clean output artifacts.
|
||||
#[clap(name = "clean")]
|
||||
Clean(clean::Clean),
|
||||
|
||||
/// Bundle the Dioxus app into a shippable object.
|
||||
#[clap(name = "bundle")]
|
||||
Bundle(bundle::Bundle),
|
||||
|
||||
/// Automatically format RSX.
|
||||
|
@ -89,6 +93,7 @@ pub(crate) enum Commands {
|
|||
|
||||
/// Dioxus config file controls.
|
||||
#[clap(subcommand)]
|
||||
#[clap(name = "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!(
|
||||
"{} ({})",
|
||||
crate::dx_build_info::PKG_VERSION,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::*;
|
||||
use crate::{serve::ServeUpdate, BuildArgs, Builder, DioxusCrate};
|
||||
use crate::{serve::ServeUpdate, BuildArgs, Builder, DioxusCrate, Result};
|
||||
|
||||
/// Run the project with the given arguments
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
|
@ -10,14 +10,14 @@ pub(crate) struct 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)
|
||||
.context("Failed to load Dioxus workspace")?;
|
||||
|
||||
self.build_args.resolve(&krate)?;
|
||||
|
||||
println!("Building crate krate data: {:#?}", krate);
|
||||
println!("Build args: {:#?}", self.build_args);
|
||||
tracing::trace!("Building crate krate data: {:#?}", krate);
|
||||
tracing::trace!("Build args: {:#?}", self.build_args);
|
||||
|
||||
let bundle = Builder::start(&krate, self.build_args.clone())?
|
||||
.finish()
|
||||
|
@ -35,11 +35,15 @@ impl RunArgs {
|
|||
// They won't generally be emitted
|
||||
loop {
|
||||
match runner.wait().await {
|
||||
ServeUpdate::StderrReceived { platform, msg } => println!("[{platform}]: {msg}"),
|
||||
ServeUpdate::StdoutReceived { platform, msg } => println!("[{platform}]: {msg}"),
|
||||
ServeUpdate::StderrReceived { platform, msg } => {
|
||||
tracing::info!("[{platform}]: {msg}")
|
||||
}
|
||||
ServeUpdate::StdoutReceived { platform, msg } => {
|
||||
tracing::info!("[{platform}]: {msg}")
|
||||
}
|
||||
ServeUpdate::ProcessExited { platform, status } => {
|
||||
runner.kill(platform);
|
||||
eprintln!("[{platform}]: process exited with status: {status:?}");
|
||||
tracing::info!("[{platform}]: process exited with status: {status:?}");
|
||||
break;
|
||||
}
|
||||
ServeUpdate::BuildUpdate { .. } => {}
|
||||
|
@ -55,6 +59,6 @@ impl RunArgs {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(StructuredOutput::Success)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use super::*;
|
||||
use crate::{AddressArguments, BuildArgs, DioxusCrate, Platform};
|
||||
use anyhow::Context;
|
||||
|
||||
/// Serve the project
|
||||
#[derive(Clone, Debug, Default, Parser)]
|
||||
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
|
||||
#[clap(name = "serve")]
|
||||
pub(crate) struct ServeArgs {
|
||||
/// The arguments for the address the server will run on
|
||||
#[clap(flatten)]
|
||||
|
@ -47,10 +45,22 @@ pub(crate) struct ServeArgs {
|
|||
|
||||
impl ServeArgs {
|
||||
/// 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)
|
||||
.context("Failed to load Dioxus workspace")?;
|
||||
///
|
||||
/// Make sure not to do any intermediate logging since our tracing infra has now enabled much
|
||||
/// 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.
|
||||
if self.hot_reload.is_none() {
|
||||
self.hot_reload = Some(krate.settings.always_hot_reload.unwrap_or(true));
|
||||
|
@ -72,9 +82,9 @@ impl ServeArgs {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use super::*;
|
||||
use crate::{Result, TraceSrc};
|
||||
use crate::{Result, StructuredOutput};
|
||||
use dioxus_rsx::{BodyNode, CallBody, TemplateBody};
|
||||
use std::{io::IsTerminal as _, process::exit};
|
||||
|
||||
/// Translate some source file into Dioxus code
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(name = "translate")]
|
||||
pub(crate) struct Translate {
|
||||
/// Activate debug mode
|
||||
// short and long flags (-d, --debug) will be deduced from the field's name
|
||||
|
@ -26,7 +24,7 @@ pub(crate) struct Translate {
|
|||
}
|
||||
|
||||
impl Translate {
|
||||
pub(crate) fn translate(self) -> Result<()> {
|
||||
pub(crate) fn translate(self) -> Result<StructuredOutput> {
|
||||
// Get the right input for the translation
|
||||
let contents = determine_input(self.file, self.raw)?;
|
||||
|
||||
|
@ -34,15 +32,18 @@ impl Translate {
|
|||
let dom = html_parser::Dom::parse(&contents)?;
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
Some(output) => std::fs::write(output, out)?,
|
||||
None => print!("{}", out),
|
||||
Some(output) => std::fs::write(output, &html)?,
|
||||
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> {
|
||||
use std::io::IsTerminal as _;
|
||||
|
||||
// Make sure not both are specified
|
||||
if file.is_some() && raw.is_some() {
|
||||
tracing::error!(dx_src = ?TraceSrc::Dev, "Only one of --file or --raw should be specified.");
|
||||
exit(0);
|
||||
return Err("Only one of --file or --raw should be specified.".into());
|
||||
}
|
||||
|
||||
if let Some(raw) = raw {
|
||||
|
|
16
packages/cli/src/cli/verbosity.rs
Normal file
16
packages/cli/src/cli/verbosity.rs
Normal 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,
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
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)]
|
||||
pub struct CustomSignCommandSettings {
|
||||
/// 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.
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,15 @@ pub(crate) static PROFILE_SERVER: &str = "dioxus-server";
|
|||
|
||||
impl DioxusCrate {
|
||||
pub(crate) fn new(target: &TargetArgs) -> Result<Self> {
|
||||
let mut cmd = Cmd::new();
|
||||
cmd.features(target.features.clone());
|
||||
|
||||
let krates = krates::Builder::new()
|
||||
tracing::debug!("Loading crate");
|
||||
let cmd = Cmd::new();
|
||||
let builder = krates::Builder::new();
|
||||
let krates = builder
|
||||
.build(cmd, |_| {})
|
||||
.context("Failed to run cargo metadata")?;
|
||||
|
||||
let package = find_main_package(&krates, target.package.clone())?;
|
||||
tracing::debug!("Found package {package:?}");
|
||||
|
||||
let dioxus_config = DioxusConfig::load(&krates, package)?.unwrap_or_default();
|
||||
|
||||
|
@ -173,6 +174,7 @@ impl DioxusCrate {
|
|||
.get_enabled_features(krate.kid)?
|
||||
.iter()
|
||||
.flat_map(|feature| {
|
||||
tracing::trace!("Autodetecting platform from feature {feature}");
|
||||
Platform::autodetect_from_cargo_feature(feature).map(|f| (f, feature.to_string()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -230,8 +232,6 @@ impl DioxusCrate {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -246,13 +246,13 @@ impl DioxusCrate {
|
|||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
// Try to find the feature that activates the dioxus feature for the given platform
|
||||
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 key == dioxus_feature {
|
||||
return Some(key.clone());
|
||||
|
@ -271,7 +271,16 @@ impl DioxusCrate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
let mut server = toml_edit::Table::new();
|
||||
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));
|
||||
}
|
||||
|
||||
if let toml_edit::Entry::Vacant(entry) = table.entry(PROFILE_ANDROID) {
|
||||
let mut android = toml_edit::Table::new();
|
||||
android.insert("inherits", Item::Value("dev".into()));
|
||||
android.insert("opt-level", Item::Value(2.into()));
|
||||
entry.insert(Item::Table(android));
|
||||
}
|
||||
}
|
||||
|
@ -561,59 +569,60 @@ impl std::fmt::Debug for DioxusCrate {
|
|||
|
||||
// Find the main package in the workspace
|
||||
fn find_main_package(krates: &Krates, package: Option<String>) -> Result<NodeId> {
|
||||
let kid = match package {
|
||||
Some(package) => {
|
||||
let mut workspace_members = krates.workspace_members();
|
||||
let found = workspace_members.find_map(|node| {
|
||||
if let krates::Node::Krate { id, krate, .. } = node {
|
||||
if krate.name == package {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
if found.is_none() {
|
||||
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());
|
||||
}
|
||||
if let Some(package) = package {
|
||||
let mut workspace_members = krates.workspace_members();
|
||||
let found = workspace_members.find_map(|node| {
|
||||
if let krates::Node::Krate { id, krate, .. } = node {
|
||||
if krate.name == package {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
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 current_dir = std::env::current_dir()?;
|
||||
let current_dir = current_dir.as_path();
|
||||
// Go through each member and find the path that is a parent of the current directory
|
||||
let mut closest_parent = None;
|
||||
for member in krates.workspace_members() {
|
||||
if let krates::Node::Krate { id, krate, .. } = member {
|
||||
let member_path = krate.manifest_path.parent().unwrap();
|
||||
if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {
|
||||
let len = path.components().count();
|
||||
match closest_parent {
|
||||
Some((_, closest_parent_len)) => {
|
||||
if len < closest_parent_len {
|
||||
closest_parent = Some((id, len));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
closest_parent = Some((id, len));
|
||||
}
|
||||
|
||||
let kid = found.ok_or_else(|| anyhow::anyhow!("Failed to find package {package}"))?;
|
||||
|
||||
return Ok(krates.nid_for_kid(kid).unwrap());
|
||||
};
|
||||
|
||||
// Otherwise find the package that is the closest parent of the current directory
|
||||
let current_dir = std::env::current_dir()?;
|
||||
let current_dir = current_dir.as_path();
|
||||
|
||||
// Go through each member and find the path that is a parent of the current directory
|
||||
let mut closest_parent = None;
|
||||
for member in krates.workspace_members() {
|
||||
if let krates::Node::Krate { id, krate, .. } = member {
|
||||
let member_path = krate.manifest_path.parent().unwrap();
|
||||
if let Ok(path) = current_dir.strip_prefix(member_path.as_std_path()) {
|
||||
let len = path.components().count();
|
||||
match closest_parent {
|
||||
Some((_, closest_parent_len)) => {
|
||||
if len < closest_parent_len {
|
||||
closest_parent = Some((id, len));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
closest_parent = Some((id, len));
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
Ok(package)
|
||||
|
|
|
@ -30,6 +30,15 @@ pub(crate) enum Error {
|
|||
#[error("Failed to establish proxy: {0}")]
|
||||
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)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
//! 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.
|
||||
|
||||
use crate::{serve::ServeUpdate, Platform as TargetPlatform};
|
||||
use crate::{serve::ServeUpdate, Cli, Commands, Platform as TargetPlatform, Verbosity};
|
||||
use cargo_metadata::{diagnostic::DiagnosticLevel, CompilerMessage};
|
||||
use clap::Parser;
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{
|
||||
|
@ -23,37 +24,107 @@ use std::{
|
|||
env,
|
||||
fmt::{Debug, Display, Write as _},
|
||||
fs,
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
sync::Mutex,
|
||||
time::Instant,
|
||||
};
|
||||
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_FILE_NAME: &str = "dx.log";
|
||||
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();
|
||||
pub static VERBOSITY: OnceCell<Verbosity> = OnceCell::new();
|
||||
|
||||
pub(crate) struct TraceController {
|
||||
pub(crate) tui_rx: UnboundedReceiver<TraceMsg>,
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn redirect() -> Self {
|
||||
let (tui_tx, tui_rx) = unbounded();
|
||||
TUI_ENABLED.store(true, Ordering::SeqCst);
|
||||
TUI_TX.set(tui_tx.clone()).unwrap();
|
||||
Self { tui_rx }
|
||||
}
|
||||
|
@ -65,54 +136,13 @@ impl TraceController {
|
|||
ServeUpdate::TracingLog { log }
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&self) {
|
||||
TUI_ENABLED.store(false, Ordering::SeqCst);
|
||||
}
|
||||
fn initialize_for_serve() {
|
||||
let filter = EnvFilter::new("error,dx=trace,dioxus-cli=trace,manganis-cli-support=trace");
|
||||
|
||||
/// Build tracing infrastructure.
|
||||
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()
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.with(file_append_layer)
|
||||
.with(cli_layer)
|
||||
.with(fmt_layer);
|
||||
|
||||
#[cfg(feature = "tokio-console")]
|
||||
let sub = sub.with(console_subscriber::spawn());
|
||||
|
||||
sub.init();
|
||||
.with(CLILayer {})
|
||||
.init();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,17 +150,27 @@ impl TraceController {
|
|||
///
|
||||
/// 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.
|
||||
struct FileAppendLayer {
|
||||
pub(crate) struct FileAppendLayer {
|
||||
file_path: PathBuf,
|
||||
buffer: Mutex<String>,
|
||||
}
|
||||
|
||||
impl FileAppendLayer {
|
||||
pub fn new(file_path: PathBuf) -> io::Result<Self> {
|
||||
Ok(Self {
|
||||
fn new() -> Self {
|
||||
let file_path = Self::log_path();
|
||||
|
||||
if !file_path.exists() {
|
||||
_ = std::fs::write(&file_path, "");
|
||||
}
|
||||
|
||||
Self {
|
||||
file_path,
|
||||
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();
|
||||
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 level = meta.level();
|
||||
|
||||
|
@ -217,14 +248,11 @@ where
|
|||
visitor.source = TraceSrc::Dev;
|
||||
}
|
||||
|
||||
TUI_TX
|
||||
_ = TUI_TX
|
||||
.get()
|
||||
.unwrap()
|
||||
.unbounded_send(TraceMsg::text(visitor.source, *level, final_msg))
|
||||
.unwrap();
|
||||
.unbounded_send(TraceMsg::text(visitor.source, *level, final_msg));
|
||||
}
|
||||
|
||||
// TODO: support spans? structured tui log display?
|
||||
}
|
||||
|
||||
/// A record visitor that collects dx-specific info and user-provided fields for logging consumption.
|
||||
|
@ -239,7 +267,6 @@ impl CollectVisitor {
|
|||
Self {
|
||||
message: String::new(),
|
||||
source: TraceSrc::Unknown,
|
||||
|
||||
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.
|
||||
fn format_field(field_name: &str, value: &dyn Debug) -> String {
|
||||
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())
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
mod assets;
|
||||
mod builder;
|
||||
mod build;
|
||||
mod bundle_utils;
|
||||
mod cli;
|
||||
mod config;
|
||||
|
@ -13,65 +13,65 @@ mod dx_build_info;
|
|||
mod error;
|
||||
mod fastfs;
|
||||
mod filemap;
|
||||
mod logging;
|
||||
mod metadata;
|
||||
mod platform;
|
||||
mod profiles;
|
||||
mod rustup;
|
||||
mod serve;
|
||||
mod settings;
|
||||
mod slog;
|
||||
mod tooling;
|
||||
mod tracer;
|
||||
|
||||
pub(crate) use builder::*;
|
||||
pub(crate) use build::*;
|
||||
pub(crate) use cli::*;
|
||||
pub(crate) use config::*;
|
||||
pub(crate) use dioxus_crate::*;
|
||||
pub(crate) use dioxus_dx_wire_format::*;
|
||||
pub(crate) use error::*;
|
||||
pub(crate) use filemap::*;
|
||||
pub(crate) use logging::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use rustup::*;
|
||||
pub(crate) use settings::*;
|
||||
pub(crate) use tracer::*;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use Commands::*;
|
||||
|
||||
#[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 let Some(link_action) = link::LinkAction::from_env() {
|
||||
return link_action.run();
|
||||
}
|
||||
|
||||
// Start the tracer so it captures logs from the build engine before we start the builder
|
||||
TraceController::initialize();
|
||||
let args = 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 {
|
||||
Translate(opts) => opts
|
||||
.translate()
|
||||
.context("⛔️ Translation of HTML into RSX failed:"),
|
||||
// Provide a structured output for third party tools that can consume the output of the CLI
|
||||
match result {
|
||||
Ok(output) => {
|
||||
tracing::debug!(json = ?output);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
?err,
|
||||
json = ?StructuredOutput::Error {
|
||||
message: format!("{err:?}"),
|
||||
},
|
||||
);
|
||||
|
||||
New(opts) => opts.create().context("🚫 Creating new project failed:"),
|
||||
|
||||
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:"),
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -174,10 +174,7 @@ impl Platform {
|
|||
Some(Platform::Linux)
|
||||
}
|
||||
}
|
||||
"mobile" => {
|
||||
tracing::warn!("Could not autodetect mobile platform. Mobile platforms must be explicitly specified. Pass `--platform ios` or `--platform android` instead.");
|
||||
None
|
||||
}
|
||||
"mobile" => None,
|
||||
"liveview" => Some(Platform::Liveview),
|
||||
"server" => Some(Platform::Server),
|
||||
_ => None,
|
||||
|
|
|
@ -68,6 +68,7 @@ impl AppHandle {
|
|||
// These need to be stable within a release version (ie 0.6.0)
|
||||
let mut envs = vec![
|
||||
("DIOXUS_CLI_ENABLED", "true".to_string()),
|
||||
("RUST_BACKTRACE", "1".to_string()),
|
||||
(
|
||||
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
|
||||
devserver_ip.to_string(),
|
||||
|
@ -116,7 +117,7 @@ impl AppHandle {
|
|||
|
||||
// https://developer.android.com/studio/run/emulator-commandline
|
||||
Platform::Android => {
|
||||
tracing::error!("Android is not yet supported, sorry!");
|
||||
self.open_android_sim(envs).await?;
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -159,7 +160,7 @@ impl AppHandle {
|
|||
// we won't actually be using the build dir.
|
||||
let asset_dir = match self.runtime_asst_dir.as_ref() {
|
||||
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:?}");
|
||||
|
@ -237,13 +238,16 @@ impl AppHandle {
|
|||
/// TODO(jon): we should probably check if there's a simulator running before trying to install,
|
||||
/// and open the simulator if we have to.
|
||||
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")
|
||||
.arg("simctl")
|
||||
.arg("install")
|
||||
.arg("booted")
|
||||
.arg(self.app.app_dir())
|
||||
.arg(self.app.build.root_dir())
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
|
@ -262,15 +266,13 @@ impl AppHandle {
|
|||
.arg("launch")
|
||||
.arg("--console")
|
||||
.arg("booted")
|
||||
.arg("com.dioxuslabs")
|
||||
.arg(self.app.bundle_identifier())
|
||||
.envs(ios_envs)
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
tracing::debug!("Launched app on simulator with exit code: {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
|
||||
|
||||
use serde_json::Value;
|
||||
let app_path = self.app.app_dir();
|
||||
let app_path = self.app.build.root_dir();
|
||||
|
||||
install_app(&app_path).await?;
|
||||
|
||||
|
@ -448,4 +450,32 @@ impl AppHandle {
|
|||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use crate::{
|
||||
BuildUpdate, Builder, DioxusCrate, Error, Platform, Result, ServeArgs, TraceController,
|
||||
TraceSrc,
|
||||
};
|
||||
use crate::{BuildUpdate, Builder, Error, Platform, Result, ServeArgs, TraceController, TraceSrc};
|
||||
|
||||
mod ansi_buffer;
|
||||
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 want us to be able to detect a `server_fn` in the project and then upgrade from a static server
|
||||
/// to a dynamic one on the fly.
|
||||
pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()> {
|
||||
pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
||||
// Redirect all logging the cli logger
|
||||
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
|
||||
let mut builder = Builder::start(&krate, args.build_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
|
||||
// Also, these commands might not be the most important, but it's all we've got enabled right now
|
||||
tracing::info!(
|
||||
r#"Serving your Dioxus app: {} 🚀
|
||||
|
||||
- Press `ctrl+c` to exit the server
|
||||
- Press `r` to rebuild the app
|
||||
- Press `o` to open the app
|
||||
- Press `t` to toggle cargo output
|
||||
- Press `/` for more commands and shortcuts
|
||||
|
||||
Learn more at https://dioxuslabs.com/learn/0.6/getting_started"#,
|
||||
r#"-----------------------------------------------------------------
|
||||
Serving your Dioxus app: {} 🚀
|
||||
• Press `ctrl+c` to exit the server
|
||||
• Press `r` to rebuild the app
|
||||
• Press `o` to open the app
|
||||
• Press `v` to toggle verbose logging
|
||||
• Press `/` for more commands and shortcuts
|
||||
Learn more at https://dioxuslabs.com/learn/0.6/getting_started
|
||||
----------------------------------------------------------------"#,
|
||||
krate.executable_name()
|
||||
);
|
||||
|
||||
|
@ -146,7 +147,7 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
|
|||
screen.new_build_update(&update);
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// `Hotreloading:` to keep the alignment during long edit sessions
|
||||
tracing::info!("Full rebuild: triggered manually");
|
||||
|
||||
builder.rebuild(args.build_arguments.clone());
|
||||
runner.file_map.force_rebuild();
|
||||
devserver.start_build().await
|
||||
|
@ -248,7 +250,6 @@ pub(crate) async fn serve_all(args: ServeArgs, krate: DioxusCrate) -> Result<()>
|
|||
_ = devserver.shutdown().await;
|
||||
_ = screen.shutdown();
|
||||
builder.abort_all();
|
||||
tracer.shutdown();
|
||||
|
||||
if let Err(err) = err {
|
||||
eprintln!("Exiting with error: {}", err);
|
||||
|
|
|
@ -99,8 +99,8 @@ impl Output {
|
|||
more_modal_open: false,
|
||||
pending_logs: VecDeque::new(),
|
||||
throbber: RefCell::new(throbber_widgets_tui::ThrobberState::default()),
|
||||
trace: cfg.trace,
|
||||
verbose: cfg.verbose,
|
||||
trace: crate::logging::VERBOSITY.get().unwrap().trace,
|
||||
verbose: crate::logging::VERBOSITY.get().unwrap().verbose,
|
||||
tick_animation: false,
|
||||
tick_interval: {
|
||||
let mut interval = tokio::time::interval(Duration::from_millis(TICK_RATE_MS));
|
||||
|
@ -152,10 +152,8 @@ impl Output {
|
|||
.execute(DisableBracketedPaste)?;
|
||||
disable_raw_mode()?;
|
||||
|
||||
// print a few lines to not cut off the output
|
||||
for _ in 0..3 {
|
||||
println!();
|
||||
}
|
||||
// print a line to force the cursor down (no tearing)
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -331,10 +329,12 @@ impl Output {
|
|||
/// we won't be drawing it.
|
||||
pub(crate) fn new_build_update(&mut self, update: &BuildUpdate) {
|
||||
match update {
|
||||
BuildUpdate::CompilerMessage { .. } => {}
|
||||
BuildUpdate::Progress { .. } => self.tick_animation = true,
|
||||
BuildUpdate::Progress {
|
||||
stage: BuildStage::Starting { .. },
|
||||
} => self.tick_animation = true,
|
||||
BuildUpdate::BuildReady { .. } => self.tick_animation = false,
|
||||
BuildUpdate::BuildFailed { .. } => self.tick_animation = false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +381,7 @@ impl Output {
|
|||
let mut area = frame.area();
|
||||
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::Fill(1),
|
||||
Constraint::Length(1),
|
||||
|
@ -391,7 +391,6 @@ impl Output {
|
|||
|
||||
self.render_borders(frame, area);
|
||||
self.render_body(frame, body, state);
|
||||
self.render_bottom_row(frame, bottom, state);
|
||||
self.render_body_title(frame, _top, state);
|
||||
}
|
||||
|
||||
|
@ -512,6 +511,7 @@ impl Output {
|
|||
BuildStage::Failed => lines.push("Failed".red()),
|
||||
BuildStage::Aborted => lines.push("Aborted".red()),
|
||||
BuildStage::Restarting => lines.push("Restarting".yellow()),
|
||||
_ => {}
|
||||
};
|
||||
|
||||
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
|
||||
fn render_borders(&self, frame: &mut Frame, area: Rect) {
|
||||
frame.render_widget(ratatui::widgets::Clear, area);
|
||||
|
@ -784,7 +767,7 @@ impl Output {
|
|||
|
||||
// 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
|
||||
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
|
||||
let lines = output_sequence.lines().collect::<Vec<_>>();
|
||||
|
@ -863,94 +846,94 @@ impl Output {
|
|||
false => VIEWPORT_HEIGHT_SMALL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String {
|
||||
let rendered = match log.content {
|
||||
TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(),
|
||||
TraceContent::Text(text) => text,
|
||||
};
|
||||
fn tracemsg_to_ansi_string(log: TraceMsg, term_width: u16) -> String {
|
||||
let rendered = match log.content {
|
||||
TraceContent::Cargo(msg) => msg.message.rendered.unwrap_or_default(),
|
||||
TraceContent::Text(text) => text,
|
||||
};
|
||||
|
||||
// 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
|
||||
//
|
||||
// We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them
|
||||
// dark gray, just for readability.
|
||||
//
|
||||
// todo(jon): refactor this out to accept any widget, not just paragraphs
|
||||
let paragraph = Paragraph::new({
|
||||
use ansi_to_tui::IntoText;
|
||||
use chrono::Timelike;
|
||||
// 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
|
||||
//
|
||||
// We make a special case for lines that look like frames (ie ==== or ---- or ------) and make them
|
||||
// dark gray, just for readability.
|
||||
//
|
||||
// todo(jon): refactor this out to accept any widget, not just paragraphs
|
||||
let paragraph = Paragraph::new({
|
||||
use ansi_to_tui::IntoText;
|
||||
use chrono::Timelike;
|
||||
|
||||
let mut text = Text::default();
|
||||
let mut text = Text::default();
|
||||
|
||||
for (idx, raw_line) in rendered.lines().enumerate() {
|
||||
let line_as_text = raw_line.into_text().unwrap();
|
||||
let is_pretending_to_be_frame = raw_line
|
||||
.chars()
|
||||
.all(|c| c == '=' || c == '-' || c == ' ' || c == '─');
|
||||
for (idx, raw_line) in rendered.lines().enumerate() {
|
||||
let line_as_text = raw_line.into_text().unwrap();
|
||||
let is_pretending_to_be_frame = raw_line
|
||||
.chars()
|
||||
.all(|c| c == '=' || c == '-' || c == ' ' || c == '─');
|
||||
|
||||
for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() {
|
||||
let mut out_line = if idx == 0 && subline_idx == 0 {
|
||||
let mut formatted_line = Line::default();
|
||||
for (subline_idx, line) in line_as_text.lines.into_iter().enumerate() {
|
||||
let mut out_line = if idx == 0 && subline_idx == 0 {
|
||||
let mut formatted_line = Line::default();
|
||||
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"{:02}:{:02}:{:02} ",
|
||||
log.timestamp.hour(),
|
||||
log.timestamp.minute(),
|
||||
log.timestamp.second()
|
||||
))
|
||||
.dark_gray(),
|
||||
);
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"[{src}] {padding}",
|
||||
src = log.source,
|
||||
padding =
|
||||
" ".repeat(3usize.saturating_sub(log.source.to_string().len()))
|
||||
))
|
||||
.style(match log.source {
|
||||
TraceSrc::App(_platform) => Style::new().blue(),
|
||||
TraceSrc::Dev => Style::new().magenta(),
|
||||
TraceSrc::Build => Style::new().yellow(),
|
||||
TraceSrc::Bundle => Style::new().magenta(),
|
||||
TraceSrc::Cargo => Style::new().yellow(),
|
||||
TraceSrc::Unknown => Style::new().gray(),
|
||||
}),
|
||||
);
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"{:02}:{:02}:{:02} ",
|
||||
log.timestamp.hour(),
|
||||
log.timestamp.minute(),
|
||||
log.timestamp.second()
|
||||
))
|
||||
.dark_gray(),
|
||||
);
|
||||
formatted_line.push_span(
|
||||
Span::raw(format!(
|
||||
"[{src}] {padding}",
|
||||
src = log.source,
|
||||
padding =
|
||||
" ".repeat(3usize.saturating_sub(log.source.to_string().len()))
|
||||
))
|
||||
.style(match log.source {
|
||||
TraceSrc::App(_platform) => Style::new().blue(),
|
||||
TraceSrc::Dev => Style::new().magenta(),
|
||||
TraceSrc::Build => Style::new().yellow(),
|
||||
TraceSrc::Bundle => Style::new().magenta(),
|
||||
TraceSrc::Cargo => Style::new().yellow(),
|
||||
TraceSrc::Unknown => Style::new().gray(),
|
||||
}),
|
||||
);
|
||||
|
||||
for span in line.spans {
|
||||
formatted_line.push_span(span);
|
||||
for span in line.spans {
|
||||
formatted_line.push_span(span);
|
||||
}
|
||||
|
||||
formatted_line
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
if is_pretending_to_be_frame {
|
||||
out_line = out_line.dark_gray();
|
||||
}
|
||||
|
||||
formatted_line
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
if is_pretending_to_be_frame {
|
||||
out_line = out_line.dark_gray();
|
||||
text.lines.push(out_line);
|
||||
}
|
||||
|
||||
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)
|
||||
//
|
||||
// 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
|
||||
// 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...
|
||||
//
|
||||
// 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
|
||||
// cells as characters. That might not actually be important, but we want to shrink the buffer
|
||||
// before printing it
|
||||
let line_count = paragraph.line_count(term_width);
|
||||
AnsiStringBuffer::new(3000, line_count as u16).render(¶graph)
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// before printing it
|
||||
let line_count = paragraph.line_count(term_width);
|
||||
AnsiStringBuffer::new(3000, line_count as u16).render(¶graph)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,16 @@ impl AppRunner {
|
|||
// todo(jon): we should allow rebinding to the same port in fullstack itself
|
||||
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
|
||||
let mut handle = AppHandle::new(app).await?;
|
||||
handle
|
||||
|
@ -120,6 +130,7 @@ impl AppRunner {
|
|||
self.builds_opened == 0 && should_open_web,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.builds_opened += 1;
|
||||
self.running.insert(platform, handle);
|
||||
|
||||
|
@ -264,17 +275,17 @@ impl AppRunner {
|
|||
.arg("simctl")
|
||||
.arg("get_app_container")
|
||||
.arg("booted")
|
||||
.arg("com.dioxuslabs")
|
||||
.arg(runner.app.bundle_identifier())
|
||||
.output()
|
||||
.await;
|
||||
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,7 +204,11 @@ impl WebServer {
|
|||
}
|
||||
|
||||
/// Sends an updated build status to all clients.
|
||||
pub(crate) async fn new_build_update(&mut self, update: &BuildUpdate) {
|
||||
pub(crate) async fn new_build_update(
|
||||
&mut self,
|
||||
update: &BuildUpdate,
|
||||
builder: &super::Builder,
|
||||
) {
|
||||
match update {
|
||||
BuildUpdate::Progress { stage } => {
|
||||
// Todo(miles): wire up more messages into the splash screen UI
|
||||
|
@ -220,11 +224,13 @@ impl WebServer {
|
|||
krate,
|
||||
..
|
||||
} => {
|
||||
self.build_status.set(Status::Building {
|
||||
progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),
|
||||
build_message: format!("{krate} compiling"),
|
||||
});
|
||||
self.send_build_status().await;
|
||||
if !builder.is_finished() {
|
||||
self.build_status.set(Status::Building {
|
||||
progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),
|
||||
build_message: format!("{krate} compiling"),
|
||||
});
|
||||
self.send_build_status().await;
|
||||
}
|
||||
}
|
||||
BuildStage::OptimizingWasm {} => {}
|
||||
BuildStage::Aborted => {}
|
||||
|
@ -250,7 +256,7 @@ impl WebServer {
|
|||
return;
|
||||
}
|
||||
|
||||
tracing::debug!("Sending hotreload to clients {:?}", reload);
|
||||
tracing::trace!("Sending hotreload to clients {:?}", reload);
|
||||
|
||||
let msg = DevserverMsg::HotReload(reload);
|
||||
let msg = serde_json::to_string(&msg).unwrap();
|
||||
|
@ -281,6 +287,8 @@ impl WebServer {
|
|||
|
||||
/// Tells all clients to reload if possible for new changes.
|
||||
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;
|
||||
|
||||
self.build_status.set(Status::Ready);
|
||||
|
|
1
packages/cli/src/slog.rs
Normal file
1
packages/cli/src/slog.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -19,7 +19,6 @@ quote = { workspace = true }
|
|||
[features]
|
||||
default = []
|
||||
fullstack = []
|
||||
static-generation = []
|
||||
desktop = []
|
||||
mobile = []
|
||||
web = []
|
||||
|
|
|
@ -33,6 +33,5 @@ define_config_macro!(web if feature = "web");
|
|||
define_config_macro!(desktop if feature = "desktop");
|
||||
define_config_macro!(mobile if feature = "mobile");
|
||||
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!(liveview if feature = "liveview");
|
||||
|
|
3
packages/desktop/.vscode/settings.json
vendored
Normal file
3
packages/desktop/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
// "rust-analyzer.cargo.target": "aarch64-linux-android"
|
||||
}
|
|
@ -83,6 +83,10 @@ objc_id = "0.1.1"
|
|||
# use rustls on android
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
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
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
|
@ -97,11 +101,12 @@ objc = "0.2.7"
|
|||
lazy-js-bundle = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["tokio_runtime", "wry/objc-exception", "devtools"]
|
||||
default = ["tokio_runtime", "exception", "devtools"]
|
||||
tokio_runtime = ["dep:tokio"]
|
||||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
devtools = ["wry/devtools", "dep:dioxus-devtools", "dioxus-signals"]
|
||||
exception = ["wry/objc-exception"]
|
||||
gnu = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
|
|
@ -75,7 +75,7 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, desktop_config: Conf
|
|||
webview.dom.in_runtime(|| {
|
||||
ScopeId::ROOT.in_runtime(|| {
|
||||
let e = eval(
|
||||
r#"
|
||||
r#"
|
||||
const xPos = await dioxus.recv();
|
||||
const yPos = await dioxus.recv();
|
||||
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"))]
|
||||
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.
|
||||
|
|
|
@ -84,6 +84,17 @@ fn serve_asset(request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>> {
|
|||
.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
|
||||
// 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`
|
||||
|
@ -261,3 +272,42 @@ fn get_mime_by_ext(trimmed: &Path) -> &'static str {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,26 +37,13 @@ pub fn connect(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send +
|
|||
std::thread::spawn(move || {
|
||||
let (mut websocket, _req) = match tungstenite::connect(endpoint.clone()) {
|
||||
Ok((websocket, req)) => (websocket, req),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to connect to devserver at {} because {}",
|
||||
endpoint, err
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
while let Ok(msg) = websocket.read() {
|
||||
match msg {
|
||||
tungstenite::Message::Text(text) => {
|
||||
if let Ok(msg) = serde_json::from_str(&text) {
|
||||
callback(msg);
|
||||
} else {
|
||||
eprintln!("Failed to parse message from devserver: {:?}", text);
|
||||
}
|
||||
}
|
||||
msg => {
|
||||
println!("Received a non-text message: {:?}", msg);
|
||||
if let tungstenite::Message::Text(text) = msg {
|
||||
if let Ok(msg) = serde_json::from_str(&text) {
|
||||
callback(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,11 @@ dioxus-web = { workspace = true, default-features = false, optional = true }
|
|||
dioxus-mobile = { workspace = true, optional = true }
|
||||
dioxus-desktop = { workspace = true, default-features = true, optional = true }
|
||||
dioxus-fullstack = { workspace = true, default-features = true, optional = true }
|
||||
dioxus-static-site-generation = { workspace = true, optional = true }
|
||||
dioxus-liveview = { workspace = true, optional = true }
|
||||
dioxus-ssr = { workspace = true, optional = true }
|
||||
manganis = { workspace = true, 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]
|
||||
dioxus-devtools = { workspace = true, optional = true }
|
||||
|
@ -55,12 +53,10 @@ router = ["dep:dioxus-router"]
|
|||
fullstack = ["dep:dioxus-fullstack", "dioxus-config-macro/fullstack", "dep:serde"]
|
||||
desktop = ["dep:dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
|
||||
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"]
|
||||
liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"]
|
||||
static-generation = ["dep:dioxus-static-site-generation", "dioxus-config-macro/static-generation"]
|
||||
axum = ["server"]
|
||||
server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "dioxus-static-site-generation?/server", "ssr", "dioxus-liveview?/axum", "dep:axum"]
|
||||
server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "ssr", "dioxus-liveview?/axum"]
|
||||
|
||||
# This feature just disables the no-renderer-enabled warning
|
||||
third-party-renderer = []
|
||||
|
@ -90,6 +86,5 @@ features = [
|
|||
"hooks",
|
||||
"html",
|
||||
"liveview",
|
||||
"static-generation",
|
||||
"server"
|
||||
]
|
||||
|
|
|
@ -16,6 +16,7 @@ pub struct LaunchBuilder {
|
|||
}
|
||||
|
||||
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
|
||||
pub type ContextFn = Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>;
|
||||
|
||||
|
@ -32,10 +33,9 @@ impl LaunchBuilder {
|
|||
feature = "mobile",
|
||||
feature = "web",
|
||||
feature = "fullstack",
|
||||
feature = "static-generation"
|
||||
))),
|
||||
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 {
|
||||
|
@ -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.
|
||||
#[cfg(feature = "mobile")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
|
||||
|
@ -188,19 +172,10 @@ impl LaunchBuilder {
|
|||
(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.
|
||||
pub fn launch(self, app: fn() -> Element) {
|
||||
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
|
||||
|
@ -209,7 +184,6 @@ impl LaunchBuilder {
|
|||
/// - `fullstack`
|
||||
/// - `desktop`
|
||||
/// - `mobile`
|
||||
/// - `static-generation`
|
||||
/// - `web`
|
||||
/// - `liveview`
|
||||
mod current_platform {
|
||||
|
@ -227,20 +201,12 @@ mod current_platform {
|
|||
not(feature = "desktop"),
|
||||
not(all(feature = "fullstack", feature = "server"))
|
||||
))]
|
||||
pub use dioxus_mobile::launch::*;
|
||||
|
||||
#[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::*;
|
||||
pub use dioxus_mobile::launch_bindings::*;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "web",
|
||||
not(all(feature = "fullstack", feature = "server")),
|
||||
not(all(feature = "static-generation", feature = "server")),
|
||||
not(all(feature = "server")),
|
||||
not(feature = "desktop"),
|
||||
not(feature = "mobile"),
|
||||
))]
|
||||
|
@ -255,7 +221,7 @@ mod current_platform {
|
|||
#[cfg(all(
|
||||
feature = "liveview",
|
||||
not(all(feature = "fullstack", feature = "server")),
|
||||
not(all(feature = "static-generation", feature = "server")),
|
||||
not(all(feature = "server")),
|
||||
not(feature = "desktop"),
|
||||
not(feature = "mobile"),
|
||||
not(feature = "web"),
|
||||
|
@ -265,7 +231,7 @@ mod current_platform {
|
|||
#[cfg(not(any(
|
||||
feature = "liveview",
|
||||
all(feature = "fullstack", feature = "server"),
|
||||
all(feature = "static-generation", feature = "server"),
|
||||
all(feature = "server"),
|
||||
feature = "desktop",
|
||||
feature = "mobile",
|
||||
feature = "web",
|
||||
|
@ -274,7 +240,7 @@ mod current_platform {
|
|||
root: fn() -> dioxus_core::Element,
|
||||
contexts: Vec<super::ContextFn>,
|
||||
platform_config: Vec<Box<dyn std::any::Any>>,
|
||||
) -> ! {
|
||||
) {
|
||||
#[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.");
|
||||
|
||||
|
@ -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
|
||||
macro_rules! impl_launch {
|
||||
($($return_type:tt),*) => {
|
||||
/// 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)
|
||||
}
|
||||
};
|
||||
pub fn launch(app: fn() -> Element) {
|
||||
#[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")]
|
||||
fn web_launch(
|
||||
root: fn() -> dioxus_core::Element,
|
||||
|
@ -306,7 +260,7 @@ fn web_launch(
|
|||
platform_config: Vec<Box<dyn std::any::Any>>,
|
||||
) {
|
||||
// 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
|
||||
.into_iter()
|
||||
|
@ -319,12 +273,11 @@ fn web_launch(
|
|||
for context in contexts {
|
||||
vdom.insert_any_root_context(context());
|
||||
}
|
||||
|
||||
#[cfg(feature = "document")]
|
||||
{
|
||||
#[cfg(feature = "fullstack")]
|
||||
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)
|
||||
as std::rc::Rc<dyn crate::prelude::document::Document>;
|
||||
vdom.provide_root_context(document);
|
||||
|
@ -334,6 +287,7 @@ fn web_launch(
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
//! - `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
|
||||
//! - `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
|
||||
//! - `axum`: enables the axum server with static generation or fullstack and sets the platform to server mode
|
||||
//! - `server`: enables the server variant of dioxus
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
@ -119,13 +118,6 @@ pub mod prelude {
|
|||
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
|
||||
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_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
pub use dioxus_router;
|
||||
|
@ -134,10 +126,6 @@ pub mod prelude {
|
|||
#[cfg_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
pub use dioxus_router::prelude::*;
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
pub use axum;
|
||||
|
||||
#[cfg(feature = "asset")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "asset")))]
|
||||
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")))]
|
||||
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_attr(docsrs, doc(cfg(feature = "desktop")))]
|
||||
pub use dioxus_desktop as desktop;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue