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:
1 parent
8a998e7d49
commit
ac3e33af46
116 files changed
+2367
-6091
No files matched your search
@@ -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 +0,0 @@
|
||||
app-dir/
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.example.androidfinal
|
||||
|
||||
class MainActivity : WryActivity()
|
||||
@@ -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
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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>
|
||||
Whitespace-only changes.
Whitespace-only changes.
+30
@@ -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>
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
Binary file not shown.
+6
@@ -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
@@ -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
@@ -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
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
File renamed without changes.
@@ -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());
|
||||
|
||||
+164
-77
@@ -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")];
|
||||
if bundle_settings.resources_map.is_none() {
|
||||
bundle_settings.resources_map = Some(HashMap::new());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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:?}");
|
||||
|
||||
// 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());
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
+37
-37
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "dioxus-dx-wire-format"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
publish = false
|
||||
authors = ["Jonathan Kelley"]
|
||||
description = "Wire format for the Dioxus CLI"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com/learn/0.5/"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
rust-version = "1.79.0"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
cargo_metadata = { workspace = true }
|
||||
@@ -0,0 +1,68 @@
|
||||
use cargo_metadata::CompilerMessage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// The structured output for the CLI
|
||||
///
|
||||
/// This is designed such that third party tools can reliably consume the output of the CLI when
|
||||
/// outputting json.
|
||||
///
|
||||
/// Not every log outputted will be parsable, but all structured logs should be.
|
||||
///
|
||||
/// This means the debug format of this log needs to be parsable json, not the default debug format.
|
||||
///
|
||||
/// We guarantee that the last line of the command represents the success of the command, such that
|
||||
/// tools can simply parse the last line of the output.
|
||||
///
|
||||
/// There might be intermediate lines that are parseable as structured logs (which you can put here)
|
||||
/// but they are not guaranteed to be, such that we can provide better error messages for the user.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub enum StructuredOutput {
|
||||
BuildFinished { path: PathBuf },
|
||||
BuildUpdate { stage: BuildStage },
|
||||
CargoOutput { message: CompilerMessage },
|
||||
BundleOutput { bundles: Vec<PathBuf> },
|
||||
HtmlTranslate { html: String },
|
||||
Success,
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for StructuredOutput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&serde_json::to_string(self).map_err(|_e| std::fmt::Error)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// The current stage of the ongoing build
|
||||
///
|
||||
/// This is a perma-unstable interface that is subject to change at any time.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BuildStage {
|
||||
Initializing,
|
||||
Starting {
|
||||
crate_count: usize,
|
||||
is_server: bool,
|
||||
},
|
||||
InstallingTooling,
|
||||
Compiling {
|
||||
is_server: bool,
|
||||
current: usize,
|
||||
total: usize,
|
||||
krate: String,
|
||||
},
|
||||
RunningBindgen,
|
||||
OptimizingWasm,
|
||||
CopyingAssets {
|
||||
current: usize,
|
||||
total: usize,
|
||||
path: PathBuf,
|
||||
},
|
||||
Bundling,
|
||||
Success,
|
||||
Failed,
|
||||
Aborted,
|
||||
Restarting,
|
||||
}
|
||||
@@ -75,7 +75,7 @@ dioxus = { version = "*", features = ["fullstack"] }
|
||||
axum = { version = "0.7.0", optional = true }
|
||||
|
||||
[features]
|
||||
server = ["dep:axum", "dioxus/server"]
|
||||
server = ["dioxus/server", "dep:axum"]
|
||||
web = ["dioxus/web"]
|
||||
```
|
||||
|
||||
@@ -145,7 +145,7 @@ dioxus = { version = "*", features = ["fullstack"] }
|
||||
axum = { version = "0.7.0", optional = true }
|
||||
|
||||
[features]
|
||||
server = ["dep:axum", "dioxus/server"]
|
||||
server = ["dioxus/server", "dep:axum"]
|
||||
web = ["dioxus/web"]
|
||||
```
|
||||
|
||||
|
||||
@@ -12,13 +12,22 @@ license = "MIT OR Apache-2.0"
|
||||
[dependencies]
|
||||
dioxus-desktop = { workspace = true }
|
||||
dioxus-lib = { workspace = true }
|
||||
jni = "0.21.1"
|
||||
libc = "0.2.159"
|
||||
once_cell.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
jni = "0.21.1"
|
||||
|
||||
[features]
|
||||
default = ["jnibindings"]
|
||||
default = ["jnibindings", "tokio_runtime", "devtools", "exception"]
|
||||
jnibindings = []
|
||||
tokio_runtime = ["dioxus-desktop/tokio_runtime"]
|
||||
fullscreen = ["dioxus-desktop/fullscreen"]
|
||||
transparent = ["dioxus-desktop/transparent"]
|
||||
devtools = ["dioxus-desktop/devtools"]
|
||||
gnu = ["dioxus-desktop/gnu"]
|
||||
exception = ["dioxus-desktop/exception"]
|
||||
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
@@ -6,15 +6,44 @@ pub use dioxus_desktop::*;
|
||||
use dioxus_lib::prelude::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub mod launch_bindings {
|
||||
use std::any::Any;
|
||||
|
||||
use super::*;
|
||||
pub fn launch(
|
||||
root: fn() -> Element,
|
||||
_contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
|
||||
_platform_config: Vec<Box<dyn Any>>,
|
||||
) {
|
||||
super::launch(root);
|
||||
}
|
||||
|
||||
pub fn launch_virtual_dom(_virtual_dom: VirtualDom, _desktop_config: Config) -> ! {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch via the binding API
|
||||
pub fn launch(incoming: fn() -> Element) {
|
||||
*APP_FN_PTR.lock().unwrap() = Some(incoming);
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
*APP_FN_PTR.lock().unwrap() = Some(incoming);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
dioxus_desktop::launch::launch(incoming, vec![], Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
static APP_FN_PTR: Mutex<Option<fn() -> Element>> = Mutex::new(None);
|
||||
|
||||
pub fn root() {
|
||||
let app = APP_FN_PTR.lock().unwrap().unwrap();
|
||||
let app = APP_FN_PTR
|
||||
.lock()
|
||||
.expect("APP_FN_PTR lock failed")
|
||||
.expect("Android to have set the app trampoline");
|
||||
|
||||
dioxus_desktop::launch::launch(app, vec![], Default::default());
|
||||
}
|
||||
|
||||
@@ -24,13 +53,13 @@ pub fn root() {
|
||||
pub extern "C" fn start_app() {
|
||||
tao::android_binding!(
|
||||
com_example,
|
||||
wrytest,
|
||||
androidfinal,
|
||||
WryActivity,
|
||||
wry::android_setup,
|
||||
root,
|
||||
tao
|
||||
);
|
||||
wry::android_binding!(com_example, wrytest, wry);
|
||||
wry::android_binding!(com_example, androidfinal, wry);
|
||||
}
|
||||
|
||||
/// Call our `main` function to initialize the rust runtime and set the launch binding trampoline
|
||||
|
||||
@@ -13,5 +13,5 @@ tokio = { workspace = true, features = ["full"], optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/axum", "dep:tokio"]
|
||||
server = ["dioxus/server", "dep:tokio"]
|
||||
web = ["dioxus/web"]
|
||||
@@ -13,5 +13,5 @@ tokio = { workspace = true, features = ["full"], optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/axum", "tokio"]
|
||||
server = ["dioxus/server", "tokio"]
|
||||
web = ["dioxus/web"]
|
||||
@@ -11,5 +11,5 @@ serde = "1.0.159"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/axum"]
|
||||
server = ["dioxus/server"]
|
||||
web = ["dioxus/web"]
|
||||
@@ -1 +0,0 @@
|
||||
target
|
||||
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "dioxus-static-site-generation"
|
||||
authors = ["Evan Almloff"]
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
description = "Static site generation for Dioxus"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
keywords = ["ui", "gui", "react", "ssg"]
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
dioxus-fullstack = { workspace = true }
|
||||
dioxus-lib.workspace = true
|
||||
dioxus-router = { workspace = true }
|
||||
dioxus-ssr = { workspace = true, optional = true }
|
||||
dioxus-isrg = { workspace = true, optional = true }
|
||||
axum = { workspace = true, features = ["ws", "macros"], optional = true }
|
||||
tower-http = { workspace = true, features = ["fs"], optional = true }
|
||||
dioxus-devtools = { workspace = true, optional = true }
|
||||
dioxus-cli-config = { workspace = true, optional = true }
|
||||
dioxus-web = { workspace = true, features = ["hydrate"], optional = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
http = { workspace = true, optional = true }
|
||||
tower = { workspace = true, features = ["util"], optional = true }
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true }
|
||||
criterion = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus-fullstack/server", "dep:dioxus-ssr", "dep:tokio", "dep:http", "dep:axum", "dep:tower-http", "dep:dioxus-devtools", "dep:dioxus-cli-config", "dep:tower", "dep:dioxus-isrg"]
|
||||
web = ["dioxus-fullstack/web", "dep:dioxus-web"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||
@@ -1,83 +0,0 @@
|
||||
# Dioxus Fullstack
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/dioxus-fullstack.svg
|
||||
[crates-url]: https://crates.io/crates/dioxus-fullstack
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT
|
||||
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
|
||||
[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster
|
||||
[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/XgGxMSkvUM
|
||||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[Guides](https://dioxuslabs.com/learn/0.5/) |
|
||||
[API Docs](https://docs.rs/dioxus-fullstack/latest) |
|
||||
[Chat](https://discord.gg/XgGxMSkvUM)
|
||||
|
||||
Fullstack utilities for the [`Dioxus`](https://dioxuslabs.com) framework.
|
||||
|
||||
# Features
|
||||
|
||||
- Integrates with the [Axum](../fullstack/examples/axum-hello-world/src/main.rs) server framework with utilities for serving and rendering Dioxus applications.
|
||||
- [Server functions](https://docs.rs/dioxus-fullstack/latest/dioxus_fullstack/prelude/attr.server.html) allow you to call code on the server from the client as if it were a normal function.
|
||||
- Instant RSX Hot reloading with [`dioxus-hot-reload`](https://crates.io/crates/dioxus-hot-reload).
|
||||
- Passing root props from the server to the client.
|
||||
|
||||
# Example
|
||||
|
||||
Full stack Dioxus in under 30 lines of code
|
||||
|
||||
```rust
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
let meaning = use_signal(|| None);
|
||||
|
||||
rsx! {
|
||||
h1 { "Meaning of life: {meaning:?}" }
|
||||
button {
|
||||
onclick: move |_| async move {
|
||||
if let Ok(data) = get_meaning("life the universe and everything".into()).await {
|
||||
meaning.set(data);
|
||||
}
|
||||
},
|
||||
"Run a server function"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn get_meaning(of: String) -> Result<Option<u32>, ServerFnError> {
|
||||
Ok(of.contains("life").then(|| 42))
|
||||
}
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
To get started with full stack Dioxus, check out our [getting started guide](https://dioxuslabs.com/learn/0.5/getting_started), or the [examples examples](https://github.com/DioxusLabs/dioxus/tree/master/examples).
|
||||
|
||||
## Contributing
|
||||
|
||||
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
||||
- Join the discord and ask questions!
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Dioxus by you shall be licensed as MIT without any additional
|
||||
terms or conditions.
|
||||
@@ -1,180 +0,0 @@
|
||||
//! Launch helper macros for fullstack apps
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use dioxus_lib::prelude::dioxus_core::LaunchConfig;
|
||||
|
||||
/// Settings for a statically generated site that may be hydrated in the browser
|
||||
pub struct Config {
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) output_dir: PathBuf,
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) index_html: Option<String>,
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) index_path: Option<PathBuf>,
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) map_path: Option<Box<dyn Fn(&str) -> PathBuf + Send + Sync + 'static>>,
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) root_id: Option<&'static str>,
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) additional_routes: Vec<String>,
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) github_pages: bool,
|
||||
}
|
||||
|
||||
impl LaunchConfig for Config {}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "server")]
|
||||
output_dir: PathBuf::from("./static"),
|
||||
#[cfg(feature = "server")]
|
||||
index_html: None,
|
||||
#[cfg(feature = "server")]
|
||||
index_path: None,
|
||||
#[cfg(feature = "server")]
|
||||
map_path: None,
|
||||
#[cfg(feature = "server")]
|
||||
root_id: None,
|
||||
#[cfg(feature = "server")]
|
||||
additional_routes: vec!["/".to_string()],
|
||||
#[cfg(feature = "server")]
|
||||
github_pages: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This config intentionally leaves server options in even if the server feature is not enabled to make it easier to use the config without moving different parts of the builder under configs.
|
||||
impl Config {
|
||||
/// Create a new config for a static site generation app.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set a mapping from the route to the file path. This will override the default mapping configured with `static_dir`.
|
||||
/// The function should return the path to the folder to store the index.html file in.
|
||||
///
|
||||
/// This method will only effect static site generation.
|
||||
#[allow(unused)]
|
||||
pub fn map_path<F: Fn(&str) -> PathBuf + Send + Sync + 'static>(mut self, map_path: F) -> Self {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
self.map_path = Some(Box::new(map_path));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the output directory for the static site generation. (defaults to ./static)
|
||||
/// This is the directory that will be used to store the generated static html files.
|
||||
///
|
||||
/// This method will only effect static site generation.
|
||||
#[allow(unused)]
|
||||
pub fn output_dir(mut self, output_dir: PathBuf) -> Self {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
self.output_dir = output_dir;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the id of the root element in the index.html file to place the prerendered content into. (defaults to main)
|
||||
#[allow(unused)]
|
||||
pub fn root_id(mut self, root_id: &'static str) -> Self {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
self.root_id = Some(root_id);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the contents of the index.html file to be served. (precedence over index_path)
|
||||
///
|
||||
/// This method will only effect static site generation.
|
||||
#[allow(unused)]
|
||||
pub fn index_html(mut self, index_html: String) -> Self {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
self.index_html = Some(index_html);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the path of the index.html file to be served. (defaults to {assets_path}/index.html)
|
||||
///
|
||||
/// This method will only effect static site generation.
|
||||
#[allow(unused)]
|
||||
pub fn index_path(mut self, index_path: PathBuf) -> Self {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
self.index_path = Some(index_path);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of static routes that will be pre-rendered and served in addition to the static routes in the router.
|
||||
#[allow(unused)]
|
||||
pub fn additional_routes(mut self, mut routes: Vec<String>) -> Self {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
self.additional_routes.append(&mut routes);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// A preset for github pages. This will output your files in the `/docs` directory and set up a `404.html` file.
|
||||
pub fn github_pages(self) -> Self {
|
||||
#[allow(unused_mut)]
|
||||
let mut myself = self
|
||||
.additional_routes(vec!["/404".into()])
|
||||
.output_dir(PathBuf::from("./docs"));
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
myself.github_pages = true;
|
||||
}
|
||||
myself
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
impl Config {
|
||||
pub(crate) fn fullstack_template(&self) -> dioxus_fullstack::prelude::FullstackHTMLTemplate {
|
||||
use dioxus_fullstack::prelude::{FullstackHTMLTemplate, ServeConfig};
|
||||
let mut cfg_builder = ServeConfig::builder();
|
||||
if let Some(index_html) = &self.index_html {
|
||||
cfg_builder = cfg_builder.index_html(index_html.clone());
|
||||
}
|
||||
if let Some(index_path) = &self.index_path {
|
||||
cfg_builder = cfg_builder.index_path(index_path.clone());
|
||||
}
|
||||
if let Some(root_id) = self.root_id {
|
||||
cfg_builder = cfg_builder.root_id(root_id);
|
||||
}
|
||||
let cfg = cfg_builder.build();
|
||||
|
||||
FullstackHTMLTemplate::new(&cfg.unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn create_cache(&mut self) -> dioxus_isrg::IncrementalRenderer {
|
||||
let mut builder =
|
||||
dioxus_isrg::IncrementalRenderer::builder().static_dir(self.output_dir.clone());
|
||||
if let Some(map_path) = self.map_path.take() {
|
||||
builder = builder.map_path(map_path);
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
pub(crate) fn create_renderer(&mut self) -> dioxus_ssr::Renderer {
|
||||
let mut renderer = dioxus_ssr::Renderer::new();
|
||||
renderer.pre_render = true;
|
||||
renderer
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
//! This module contains the `launch` function, which is the main entry point for dioxus fullstack
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use dioxus_lib::prelude::Element;
|
||||
|
||||
/// Launch a fullstack app with the given root component, contexts, and config.
|
||||
#[allow(unused)]
|
||||
pub fn launch(
|
||||
root: fn() -> Element,
|
||||
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
|
||||
platform_config: Vec<Box<dyn Any>>,
|
||||
) {
|
||||
#[cfg(feature = "server")]
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
use axum::extract::Path;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use axum::ServiceExt;
|
||||
use http::StatusCode;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_http::services::ServeFile;
|
||||
let platform_config = platform_config
|
||||
.into_iter()
|
||||
.find_map(|cfg| cfg.downcast::<crate::Config>().map(|cfg| *cfg).ok())
|
||||
.unwrap_or_else(crate::Config::new);
|
||||
|
||||
let github_pages = platform_config.github_pages;
|
||||
let path = platform_config.output_dir.clone();
|
||||
crate::ssg::generate_static_site(root, platform_config)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
//! Static generation for Dioxus
|
||||
|
||||
mod config;
|
||||
pub use config::*;
|
||||
pub mod launch;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub(crate) mod ssg;
|
||||
|
||||
/// A prelude of commonly used items in static generation apps.
|
||||
pub mod prelude {
|
||||
pub use dioxus_fullstack::prelude::*;
|
||||
}
|
||||
|
||||
/// A document provider for static generation apps
|
||||
pub mod document {
|
||||
pub use dioxus_fullstack::document::*;
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
use dioxus_isrg::*;
|
||||
use dioxus_lib::document::Document;
|
||||
use dioxus_lib::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
use dioxus_ssr::renderer;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::Config;
|
||||
|
||||
fn server_context_for_route(route: &str) -> dioxus_fullstack::prelude::DioxusServerContext {
|
||||
use dioxus_fullstack::prelude::*;
|
||||
let request = http::Request::builder().uri(route).body(()).unwrap();
|
||||
let (parts, _) = request.into_parts();
|
||||
|
||||
DioxusServerContext::new(parts)
|
||||
}
|
||||
|
||||
/// Try to extract the site map by finding the root router that a component renders.
|
||||
fn extract_site_map(app: fn() -> Element) -> Option<&'static [SiteMapSegment]> {
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
|
||||
vdom.rebuild_in_place();
|
||||
|
||||
vdom.in_runtime(|| {
|
||||
ScopeId::ROOT.in_runtime(|| dioxus_router::prelude::root_router().map(|r| r.site_map()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate a static site from any fullstack app that uses the router.
|
||||
pub async fn generate_static_site(
|
||||
app: fn() -> Element,
|
||||
mut config: Config,
|
||||
) -> Result<(), IncrementalRendererError> {
|
||||
use tokio::task::block_in_place;
|
||||
|
||||
// Create the static output dir
|
||||
std::fs::create_dir_all(&config.output_dir)?;
|
||||
|
||||
let mut renderer = config.create_renderer();
|
||||
let mut cache = config.create_cache();
|
||||
|
||||
let mut routes_to_render: HashSet<String> = config.additional_routes.iter().cloned().collect();
|
||||
if let Some(site_map) = block_in_place(|| extract_site_map(app)) {
|
||||
let flat_site_map = site_map.iter().flat_map(SiteMapSegment::flatten);
|
||||
for route in flat_site_map {
|
||||
let Some(static_route) = route
|
||||
.iter()
|
||||
.filter(|s| s.to_child().is_none())
|
||||
.map(SegmentType::to_static)
|
||||
.collect::<Option<Vec<_>>>()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let url = format!("/{}", static_route.join("/"));
|
||||
|
||||
routes_to_render.insert(url);
|
||||
}
|
||||
} else {
|
||||
tracing::trace!("No site map found, rendering the additional routes");
|
||||
}
|
||||
|
||||
for url in routes_to_render {
|
||||
prerender_route(app, url, &mut renderer, &mut cache, &config).await?;
|
||||
}
|
||||
|
||||
// Copy over the web output dir into the static output dir
|
||||
let out_path = dioxus_cli_config::out_dir().unwrap_or("./dist".into());
|
||||
|
||||
let assets_path = out_path.join("public");
|
||||
|
||||
let index_path = assets_path.join("index.html");
|
||||
let skip = vec![index_path.clone()];
|
||||
copy_static_files(&assets_path, &config.output_dir, &skip)?;
|
||||
|
||||
// Copy the output of the SSG build into the public directory so the CLI serves it
|
||||
copy_static_files(&config.output_dir, &assets_path, &[])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_static_files(src: &Path, dst: &Path, skip: &[PathBuf]) -> Result<(), std::io::Error> {
|
||||
let mut queue = vec![src.to_path_buf()];
|
||||
while let Some(path) = queue.pop() {
|
||||
if skip.contains(&path) {
|
||||
continue;
|
||||
}
|
||||
if path.is_dir() {
|
||||
for entry in fs::read_dir(&path).into_iter().flatten().flatten() {
|
||||
let path = entry.path();
|
||||
queue.push(path);
|
||||
}
|
||||
} else {
|
||||
let output_location = dst.join(path.strip_prefix(src).unwrap());
|
||||
let parent = output_location.parent().unwrap();
|
||||
if !parent.exists() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::copy(&path, output_location)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prerender_route(
|
||||
app: fn() -> Element,
|
||||
route: String,
|
||||
renderer: &mut renderer::Renderer,
|
||||
cache: &mut dioxus_isrg::IncrementalRenderer,
|
||||
config: &Config,
|
||||
) -> Result<RenderFreshness, dioxus_isrg::IncrementalRendererError> {
|
||||
use dioxus_fullstack::prelude::*;
|
||||
|
||||
let context = server_context_for_route(&route);
|
||||
let wrapper = config.fullstack_template();
|
||||
let mut virtual_dom = VirtualDom::new(app);
|
||||
let document = std::rc::Rc::new(dioxus_fullstack::document::ServerDocument::default());
|
||||
virtual_dom.provide_root_context(document.clone() as std::rc::Rc<dyn Document>);
|
||||
with_server_context(context.clone(), || {
|
||||
tokio::task::block_in_place(|| virtual_dom.rebuild_in_place());
|
||||
});
|
||||
ProvideServerContext::new(virtual_dom.wait_for_suspense(), context).await;
|
||||
|
||||
let mut wrapped = String::new();
|
||||
|
||||
// Render everything before the body
|
||||
wrapper.render_head(&mut wrapped, &virtual_dom)?;
|
||||
|
||||
renderer.render_to(&mut wrapped, &virtual_dom)?;
|
||||
|
||||
wrapper.render_after_main(&mut wrapped, &virtual_dom)?;
|
||||
wrapper.render_after_body(&mut wrapped)?;
|
||||
|
||||
cache.cache(route, wrapped)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_site_map_works() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(Clone, Routable, Debug, PartialEq)]
|
||||
enum Route {
|
||||
#[route("/")]
|
||||
Home {},
|
||||
#[route("/about")]
|
||||
About {},
|
||||
}
|
||||
|
||||
fn Home() -> Element {
|
||||
rsx! { "Home" }
|
||||
}
|
||||
|
||||
fn About() -> Element {
|
||||
rsx! { "About" }
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
Other {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn Other() -> Element {
|
||||
rsx! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
let site_map = extract_site_map(app);
|
||||
assert_eq!(site_map, Some(Route::SITE_MAP));
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub fn launch(
|
||||
for context in contexts {
|
||||
vdom.insert_any_root_context(context());
|
||||
}
|
||||
|
||||
let platform_config = *platform_config
|
||||
.into_iter()
|
||||
.find_map(|cfg| cfg.downcast::<Config>().ok())
|
||||
|
||||
Reference in new issue
Block a user