fix stutter in bundle, fix nsis, fix msi bundling (#5412)
* fix stutter in bundle, fix nsis, fix msi bundling * fmt * rename android libname to `libmain`, rename server exe to `server` * fix hot-dog url handling * set linux product name * update schema
This commit is contained in:
1 parent
a42dd1320b
commit
20e242628d
14 files changed
+157
-112
No files matched your search
@@ -20,3 +20,4 @@ desktop = ["dioxus/desktop"]
|
|||||||
native = ["dioxus/native"]
|
native = ["dioxus/native"]
|
||||||
mobile = ["dioxus/mobile"]
|
mobile = ["dioxus/mobile"]
|
||||||
server = ["dioxus/server", "dep:rusqlite"]
|
server = ["dioxus/server", "dep:rusqlite"]
|
||||||
|
production = []
|
||||||
@@ -11,7 +11,7 @@ COPY --from=planner /app/recipe.json recipe.json
|
|||||||
RUN cargo chef cook --release --recipe-path recipe.json
|
RUN cargo chef cook --release --recipe-path recipe.json
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/DioxusLabs/dioxus/refs/heads/main/.github/install.sh | bash
|
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/DioxusLabs/dioxus/refs/heads/main/.github/install.sh | bash
|
||||||
RUN /.cargo/bin/dx bundle --platform web
|
RUN /.cargo/bin/dx bundle --platform web --features production
|
||||||
|
|
||||||
FROM chef AS runtime
|
FROM chef AS runtime
|
||||||
COPY --from=builder /app/target/dx/hotdog/release/web/ /usr/local/app
|
COPY --from=builder /app/target/dx/hotdog/release/web/ /usr/local/app
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use dioxus::prelude::*;
|
|||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static DB: std::sync::LazyLock<rusqlite::Connection> = std::sync::LazyLock::new(|| {
|
static DB: std::sync::LazyLock<rusqlite::Connection> = std::sync::LazyLock::new(|| {
|
||||||
std::fs::create_dir("hotdogdb").unwrap();
|
let conn = rusqlite::Connection::open(":memory:").expect("Failed to open database");
|
||||||
let conn = rusqlite::Connection::open("hotdogdb/hotdog.db").expect("Failed to open database");
|
|
||||||
|
|
||||||
conn.execute_batch(
|
conn.execute_batch(
|
||||||
"CREATE TABLE IF NOT EXISTS dogs (
|
"CREATE TABLE IF NOT EXISTS dogs (
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ enum Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(not(feature = "server"))]
|
// only in production should we set the URL, otherwise let `dx` do the work
|
||||||
|
#[cfg(all(not(feature = "server"), feature = "production"))]
|
||||||
dioxus::fullstack::set_server_url("https://hot-dog.fly.dev");
|
dioxus::fullstack::set_server_url("https://hot-dog.fly.dev");
|
||||||
|
|
||||||
dioxus::launch(app);
|
dioxus::launch(app);
|
||||||
|
|||||||
@@ -169,13 +169,28 @@ fn get_asset_root() -> PathBuf {
|
|||||||
// $product_name/
|
// $product_name/
|
||||||
// assets/
|
// assets/
|
||||||
if cfg!(target_os = "linux") {
|
if cfg!(target_os = "linux") {
|
||||||
if let Some(product_name) = dioxus_cli_config::product_name() {
|
if let Some(lib_dir) = cur_exe
|
||||||
let path = cur_exe
|
.parent()
|
||||||
.parent()
|
.and_then(|parent| parent.parent())
|
||||||
.and_then(|parent| parent.parent())
|
.map(|root| root.join("lib"))
|
||||||
.map(|root| root.join("lib").join(product_name));
|
{
|
||||||
if let Some(asset_dir) = path.filter(|path| path.exists()) {
|
// Try the product name from the compile-time env var first
|
||||||
return asset_dir;
|
if let Some(product_name) = dioxus_cli_config::product_name() {
|
||||||
|
let path = lib_dir.join(&product_name);
|
||||||
|
if path.exists() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to scanning lib/ for a directory containing an `assets/` subfolder.
|
||||||
|
// This handles debug builds where DIOXUS_PRODUCT_NAME isn't baked in.
|
||||||
|
if let Ok(entries) = std::fs::read_dir(&lib_dir) {
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() && path.join("assets").is_dir() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity android:configChanges="orientation|screenLayout|screenSize|keyboardHidden" android:exported="true"
|
<activity android:configChanges="orientation|screenLayout|screenSize|keyboardHidden" android:exported="true"
|
||||||
android:label="@string/app_name" android:name="dev.dioxus.main.MainActivity">
|
android:label="@string/app_name" android:name="dev.dioxus.main.MainActivity">
|
||||||
<meta-data android:name="android.app.lib_name" android:value="dioxusmain" />
|
<meta-data android:name="android.app.lib_name" android:value="{{ lib_name }}" />
|
||||||
<meta-data android:name="android.app.func_name" android:value="ANativeActivity_onCreate" />
|
<meta-data android:name="android.app.func_name" android:value="ANativeActivity_onCreate" />
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
@@ -165,6 +165,13 @@
|
|||||||
"$ref": "#/definitions/AndroidIntentFilter"
|
"$ref": "#/definitions/AndroidIntentFilter"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lib_name": {
|
||||||
|
"description": "Native library name loaded by NativeActivity (without `lib` prefix and `.so` extension). Defaults to `\"main\"` (i.e. `libmain.so`). Override if you need a custom library name.",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"long_description": {
|
"long_description": {
|
||||||
"description": "Long description. Overrides `bundle.long_description` for Android builds.",
|
"description": "Long description. Overrides `bundle.long_description` for Android builds.",
|
||||||
"type": [
|
"type": [
|
||||||
|
|||||||
@@ -3611,6 +3611,10 @@ impl BuildRequest {
|
|||||||
env_vars.extend(self.android_env_vars()?);
|
env_vars.extend(self.android_env_vars()?);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Always bake the product name into the binary so bundled apps can find their assets
|
||||||
|
// at runtime regardless of build profile (the asset directory structure uses the product name).
|
||||||
|
env_vars.push((PRODUCT_NAME_ENV.into(), self.bundled_app_name().into()));
|
||||||
|
|
||||||
// If this is a release build, bake the base path and title into the binary with env vars.
|
// If this is a release build, bake the base path and title into the binary with env vars.
|
||||||
// todo: should we even be doing this? might be better being a build.rs or something else.
|
// todo: should we even be doing this? might be better being a build.rs or something else.
|
||||||
if self.release {
|
if self.release {
|
||||||
@@ -3621,7 +3625,6 @@ impl BuildRequest {
|
|||||||
APP_TITLE_ENV.into(),
|
APP_TITLE_ENV.into(),
|
||||||
self.config.web.app.title.clone().into(),
|
self.config.web.app.title.clone().into(),
|
||||||
));
|
));
|
||||||
env_vars.push((PRODUCT_NAME_ENV.into(), self.bundled_app_name().into()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble the rustflags by peering into the `.cargo/config.toml` file
|
// Assemble the rustflags by peering into the `.cargo/config.toml` file
|
||||||
@@ -3739,7 +3742,6 @@ impl BuildRequest {
|
|||||||
let java_home = tools.java_home();
|
let java_home = tools.java_home();
|
||||||
let ndk_home = tools.ndk.clone();
|
let ndk_home = tools.ndk.clone();
|
||||||
let sdk_root = tools.sdk();
|
let sdk_root = tools.sdk();
|
||||||
let artifact_dir = self.android_artifact_dir()?;
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
r#"Using android:
|
r#"Using android:
|
||||||
min_sdk_version: {min_sdk_version}
|
min_sdk_version: {min_sdk_version}
|
||||||
@@ -3749,7 +3751,6 @@ impl BuildRequest {
|
|||||||
target_cxx: {target_cxx:?}
|
target_cxx: {target_cxx:?}
|
||||||
java_home: {java_home:?}
|
java_home: {java_home:?}
|
||||||
sdk_root: {sdk_root:?}
|
sdk_root: {sdk_root:?}
|
||||||
artifact_dir: {artifact_dir:?}
|
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3762,10 +3763,6 @@ impl BuildRequest {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
env_vars.push((
|
|
||||||
"DX_ANDROID_ARTIFACT_DIR".into(),
|
|
||||||
artifact_dir.into_os_string(),
|
|
||||||
));
|
|
||||||
env_vars.push((
|
env_vars.push((
|
||||||
"DX_ANDROID_NDK_HOME".into(),
|
"DX_ANDROID_NDK_HOME".into(),
|
||||||
ndk_home.clone().into_os_string(),
|
ndk_home.clone().into_os_string(),
|
||||||
@@ -3917,7 +3914,7 @@ impl BuildRequest {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
"WRY_ANDROID_LIBRARY".to_string(),
|
"WRY_ANDROID_LIBRARY".to_string(),
|
||||||
"dioxusmain".to_string().into(),
|
self.android_lib_name().into(),
|
||||||
),
|
),
|
||||||
("WRY_ANDROID_KOTLIN_FILES_OUT_DIR".to_string(), {
|
("WRY_ANDROID_KOTLIN_FILES_OUT_DIR".to_string(), {
|
||||||
let kotlin_dir = self.wry_android_kotlin_files_out_dir();
|
let kotlin_dir = self.wry_android_kotlin_files_out_dir();
|
||||||
@@ -3951,17 +3948,6 @@ impl BuildRequest {
|
|||||||
Ok(env_vars)
|
Ok(env_vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn android_artifact_dir(&self) -> Result<PathBuf> {
|
|
||||||
let dir = self
|
|
||||||
.internal_out_dir()
|
|
||||||
.join(&self.main_target)
|
|
||||||
.join(if self.release { "release" } else { "debug" })
|
|
||||||
.join("android-artifacts")
|
|
||||||
.join(self.triple.to_string());
|
|
||||||
std::fs::create_dir_all(&dir)?;
|
|
||||||
Ok(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an estimate of the number of units in the crate. If nightly rustc is not available, this
|
/// Get an estimate of the number of units in the crate. If nightly rustc is not available, this
|
||||||
/// will return an estimate of the number of units in the crate based on cargo metadata.
|
/// will return an estimate of the number of units in the crate based on cargo metadata.
|
||||||
///
|
///
|
||||||
@@ -4092,15 +4078,24 @@ impl BuildRequest {
|
|||||||
// mac/ios are unixy and dont have an exe extension
|
// mac/ios are unixy and dont have an exe extension
|
||||||
BundleFormat::MacOS | BundleFormat::Ios => self.executable_name().to_string(),
|
BundleFormat::MacOS | BundleFormat::Ios => self.executable_name().to_string(),
|
||||||
|
|
||||||
// "server" and windows can be the same
|
// the server binary is always called "server" to avoid antivirus issues when the
|
||||||
BundleFormat::Server | BundleFormat::Windows => match self.triple.operating_system {
|
// binary name changes between builds (the folder name already identifies the project)
|
||||||
|
BundleFormat::Server => match self.triple.operating_system {
|
||||||
|
OperatingSystem::Windows => "server.exe".to_string(),
|
||||||
|
_ => "server".to_string(),
|
||||||
|
},
|
||||||
|
|
||||||
|
BundleFormat::Windows => match self.triple.operating_system {
|
||||||
OperatingSystem::Windows => format!("{}.exe", self.executable_name()),
|
OperatingSystem::Windows => format!("{}.exe", self.executable_name()),
|
||||||
_ => self.executable_name().to_string(),
|
_ => self.executable_name().to_string(),
|
||||||
},
|
},
|
||||||
|
|
||||||
// from the apk spec, the root exe is a shared library
|
// from the apk spec, the root exe is a shared library
|
||||||
// we include the user's rust code as a shared library with a fixed namespace
|
// defaults to "main" (libmain.so) per NativeActivity convention, overridable in Dioxus.toml
|
||||||
BundleFormat::Android => "libdioxusmain.so".to_string(),
|
BundleFormat::Android => {
|
||||||
|
let lib_name = self.android_lib_name();
|
||||||
|
format!("lib{lib_name}.so")
|
||||||
|
}
|
||||||
|
|
||||||
// this will be wrong, I think, but not important?
|
// this will be wrong, I think, but not important?
|
||||||
BundleFormat::Web => format!("{}_bg.wasm", self.executable_name()),
|
BundleFormat::Web => format!("{}_bg.wasm", self.executable_name()),
|
||||||
@@ -4183,6 +4178,8 @@ impl BuildRequest {
|
|||||||
app_theme: Option<String>,
|
app_theme: Option<String>,
|
||||||
supports_rtl: Option<bool>,
|
supports_rtl: Option<bool>,
|
||||||
large_heap: Option<bool>,
|
large_heap: Option<bool>,
|
||||||
|
/// Native library name (without lib prefix and .so extension)
|
||||||
|
lib_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get permission mapper from config
|
// Get permission mapper from config
|
||||||
@@ -4231,6 +4228,7 @@ impl BuildRequest {
|
|||||||
app_theme: self.config.android.application.theme.clone(),
|
app_theme: self.config.android.application.theme.clone(),
|
||||||
supports_rtl: self.config.android.application.supports_rtl,
|
supports_rtl: self.config.android.application.supports_rtl,
|
||||||
large_heap: self.config.android.application.large_heap,
|
large_heap: self.config.android.application.large_heap,
|
||||||
|
lib_name: self.android_lib_name(),
|
||||||
};
|
};
|
||||||
let hbs = handlebars::Handlebars::new();
|
let hbs = handlebars::Handlebars::new();
|
||||||
|
|
||||||
@@ -4534,6 +4532,16 @@ impl BuildRequest {
|
|||||||
&self.crate_target.name
|
&self.crate_target.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Android native library name (without `lib` prefix and `.so` extension).
|
||||||
|
/// Defaults to `"main"` per NativeActivity convention, overridable via `[android] lib_name`.
|
||||||
|
fn android_lib_name(&self) -> String {
|
||||||
|
self.config
|
||||||
|
.android
|
||||||
|
.lib_name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "main".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the type of executable we are compiling
|
/// Get the type of executable we are compiling
|
||||||
pub(crate) fn executable_type(&self) -> TargetKind {
|
pub(crate) fn executable_type(&self) -> TargetKind {
|
||||||
self.crate_target.kind[0].clone()
|
self.crate_target.kind[0].clone()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ impl BundleContext<'_> {
|
|||||||
|
|
||||||
verify_codesigned_app(app_path).await?;
|
verify_codesigned_app(app_path).await?;
|
||||||
|
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("ipa");
|
let output_dir = self.project_out_directory().join("ipa");
|
||||||
std::fs::create_dir_all(&output_dir)?;
|
std::fs::create_dir_all(&output_dir)?;
|
||||||
|
|
||||||
let ipa_name = format!(
|
let ipa_name = format!(
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl BundleContext<'_> {
|
|||||||
let arch = self.binary_arch();
|
let arch = self.binary_arch();
|
||||||
let arch_str = arch.appimage_arch();
|
let arch_str = arch.appimage_arch();
|
||||||
|
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("appimage");
|
let output_dir = self.project_out_directory().join("appimage");
|
||||||
fs::create_dir_all(&output_dir)?;
|
fs::create_dir_all(&output_dir)?;
|
||||||
|
|
||||||
let appimage_filename = format!("{name}_{version}_{arch_str}.AppImage");
|
let appimage_filename = format!("{name}_{version}_{arch_str}.AppImage");
|
||||||
@@ -150,7 +150,7 @@ impl BundleContext<'_> {
|
|||||||
let package_name = self.deb_package_name();
|
let package_name = self.deb_package_name();
|
||||||
let version = self.version_string();
|
let version = self.version_string();
|
||||||
|
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("deb");
|
let output_dir = self.project_out_directory().join("deb");
|
||||||
fs::create_dir_all(&output_dir)?;
|
fs::create_dir_all(&output_dir)?;
|
||||||
|
|
||||||
let deb_filename = format!("{package_name}_{version}_{arch}.deb");
|
let deb_filename = format!("{package_name}_{version}_{arch}.deb");
|
||||||
@@ -234,7 +234,7 @@ impl BundleContext<'_> {
|
|||||||
let description = self.short_description();
|
let description = self.short_description();
|
||||||
let resource_dir_name = self.linux_resource_dir_name();
|
let resource_dir_name = self.linux_resource_dir_name();
|
||||||
|
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("rpm");
|
let output_dir = self.project_out_directory().join("rpm");
|
||||||
fs::create_dir_all(&output_dir)?;
|
fs::create_dir_all(&output_dir)?;
|
||||||
|
|
||||||
let rpm_filename = format!("{name}-{version}-1.{arch}.rpm");
|
let rpm_filename = format!("{name}-{version}-1.{arch}.rpm");
|
||||||
@@ -864,16 +864,20 @@ Type=Application
|
|||||||
control.push_str(&format!("Installed-Size: {installed_size}\n"));
|
control.push_str(&format!("Installed-Size: {installed_size}\n"));
|
||||||
|
|
||||||
let description = self.short_description();
|
let description = self.short_description();
|
||||||
if !description.is_empty() {
|
// Description is a required field in Debian control files - use the product name as fallback
|
||||||
control.push_str(&format!("Description: {description}\n"));
|
let description = if description.is_empty() {
|
||||||
|
self.product_name()
|
||||||
|
} else {
|
||||||
|
description
|
||||||
|
};
|
||||||
|
control.push_str(&format!("Description: {description}\n"));
|
||||||
|
|
||||||
if let Some(long_desc) = self.long_description() {
|
if let Some(long_desc) = self.long_description() {
|
||||||
for line in long_desc.lines() {
|
for line in long_desc.lines() {
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
control.push_str(" .\n");
|
control.push_str(" .\n");
|
||||||
} else {
|
} else {
|
||||||
control.push_str(&format!(" {line}\n"));
|
control.push_str(&format!(" {line}\n"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl BundleContext<'_> {
|
|||||||
/// archive container.
|
/// archive container.
|
||||||
pub(crate) async fn bundle_updater(&self, bundles: &[Bundle]) -> Result<Vec<PathBuf>> {
|
pub(crate) async fn bundle_updater(&self, bundles: &[Bundle]) -> Result<Vec<PathBuf>> {
|
||||||
let mut updater_paths = Vec::new();
|
let mut updater_paths = Vec::new();
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("updater");
|
let output_dir = self.project_out_directory().join("updater");
|
||||||
std::fs::create_dir_all(&output_dir)?;
|
std::fs::create_dir_all(&output_dir)?;
|
||||||
|
|
||||||
for bundle in bundles {
|
for bundle in bundles {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ impl BundleContext<'_> {
|
|||||||
/// Intermediate `.wxs`, `.wixobj`, and `_staging` files are intentionally left in
|
/// Intermediate `.wxs`, `.wixobj`, and `_staging` files are intentionally left in
|
||||||
/// place because they are useful when diagnosing packaging failures.
|
/// place because they are useful when diagnosing packaging failures.
|
||||||
pub(crate) async fn bundle_windows_msi(&self) -> Result<Vec<PathBuf>> {
|
pub(crate) async fn bundle_windows_msi(&self) -> Result<Vec<PathBuf>> {
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("msi");
|
let output_dir = self.project_out_directory().join("msi");
|
||||||
std::fs::create_dir_all(&output_dir).context("Failed to create MSI output directory")?;
|
std::fs::create_dir_all(&output_dir).context("Failed to create MSI output directory")?;
|
||||||
|
|
||||||
let wix_settings = self.windows().wix.unwrap_or_default();
|
let wix_settings = self.windows().wix.unwrap_or_default();
|
||||||
@@ -86,9 +86,7 @@ impl BundleContext<'_> {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let resources_dir = staging_dir.join("resources");
|
self.copy_resources(&staging_dir)?;
|
||||||
std::fs::create_dir_all(&resources_dir)?;
|
|
||||||
self.copy_resources(&resources_dir)?;
|
|
||||||
self.copy_external_binaries(&staging_dir)?;
|
self.copy_external_binaries(&staging_dir)?;
|
||||||
let staged_files = collect_staged_files(&staging_dir, Some(&main_binary_dest))?;
|
let staged_files = collect_staged_files(&staging_dir, Some(&main_binary_dest))?;
|
||||||
let (install_tree, component_refs_xml) = render_wix_install_tree(&staged_files);
|
let (install_tree, component_refs_xml) = render_wix_install_tree(&staged_files);
|
||||||
@@ -213,28 +211,20 @@ impl BundleContext<'_> {
|
|||||||
serde_json::Value::String(component_refs_xml),
|
serde_json::Value::String(component_refs_xml),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut hbs = Handlebars::new();
|
let wix_template = if let Some(custom_template) = &wix_settings.template {
|
||||||
hbs.set_strict_mode(false);
|
let template_path = self.crate_dir().join(custom_template);
|
||||||
hbs.register_escape_fn(|s: &str| s.to_string());
|
std::fs::read_to_string(&template_path).with_context(|| {
|
||||||
hbs.register_template_string(
|
format!(
|
||||||
"wix",
|
"Failed to read custom WiX template: {}",
|
||||||
if let Some(custom_template) = &wix_settings.template {
|
template_path.display()
|
||||||
let template_path = self.crate_dir().join(custom_template);
|
)
|
||||||
std::fs::read_to_string(&template_path).with_context(|| {
|
})?
|
||||||
format!(
|
} else {
|
||||||
"Failed to read custom WiX template: {}",
|
WIX_TEMPLATE.to_string()
|
||||||
template_path.display()
|
};
|
||||||
)
|
|
||||||
})?
|
|
||||||
} else {
|
|
||||||
WIX_TEMPLATE.to_string()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.context("Failed to parse WiX template")?;
|
|
||||||
|
|
||||||
let wxs_content = hbs
|
let wxs_content =
|
||||||
.render("wix", &data)
|
render_template(&wix_template, &data).context("Failed to render WiX template")?;
|
||||||
.context("Failed to render WiX template")?;
|
|
||||||
|
|
||||||
let wxs_path = output_dir.join(format!("{product_name}.wxs"));
|
let wxs_path = output_dir.join(format!("{product_name}.wxs"));
|
||||||
std::fs::write(&wxs_path, &wxs_content).context("Failed to write WiX source file")?;
|
std::fs::write(&wxs_path, &wxs_content).context("Failed to write WiX source file")?;
|
||||||
@@ -376,7 +366,7 @@ impl BundleContext<'_> {
|
|||||||
/// As with the MSI flow, the script and staging directory are preserved for
|
/// As with the MSI flow, the script and staging directory are preserved for
|
||||||
/// debugging rather than treated as disposable hidden state.
|
/// debugging rather than treated as disposable hidden state.
|
||||||
pub(crate) async fn bundle_windows_nsis(&self) -> Result<Vec<PathBuf>> {
|
pub(crate) async fn bundle_windows_nsis(&self) -> Result<Vec<PathBuf>> {
|
||||||
let output_dir = self.project_out_directory().join("bundle").join("nsis");
|
let output_dir = self.project_out_directory().join("nsis");
|
||||||
std::fs::create_dir_all(&output_dir).context("Failed to create NSIS output directory")?;
|
std::fs::create_dir_all(&output_dir).context("Failed to create NSIS output directory")?;
|
||||||
|
|
||||||
let nsis_settings = self.windows().nsis.unwrap_or_default();
|
let nsis_settings = self.windows().nsis.unwrap_or_default();
|
||||||
@@ -422,9 +412,7 @@ impl BundleContext<'_> {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let resources_dir = staging_dir.join("resources");
|
self.copy_resources(&staging_dir)?;
|
||||||
std::fs::create_dir_all(&resources_dir)?;
|
|
||||||
self.copy_resources(&resources_dir)?;
|
|
||||||
self.copy_external_binaries(&staging_dir)?;
|
self.copy_external_binaries(&staging_dir)?;
|
||||||
let staged_files = collect_staged_files(&staging_dir, Some(&main_binary_dest))?;
|
let staged_files = collect_staged_files(&staging_dir, Some(&main_binary_dest))?;
|
||||||
|
|
||||||
@@ -914,14 +902,25 @@ async fn run_signtool_sign(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render a Handlebars template with the given data.
|
/// Render a Handlebars template with the given data.
|
||||||
|
///
|
||||||
|
/// Handlebars treats `\{{` as an escape sequence that outputs a literal `{{`.
|
||||||
|
/// This is a problem for Windows paths like `$INSTDIR\{{product_name}}` where
|
||||||
|
/// the backslash is a path separator, not an escape. We work around this by
|
||||||
|
/// replacing `\{{` with a placeholder before Handlebars parses the template,
|
||||||
|
/// then restoring the backslash in the rendered output.
|
||||||
fn render_template(template: &str, data: &BTreeMap<String, JsonValue>) -> Result<String> {
|
fn render_template(template: &str, data: &BTreeMap<String, JsonValue>) -> Result<String> {
|
||||||
|
const BACKSLASH_PLACEHOLDER: &str = "\x00BSEP\x00";
|
||||||
|
let replacement = String::from(BACKSLASH_PLACEHOLDER) + "{{";
|
||||||
|
let safe_template = template.replace("\\{{", &replacement);
|
||||||
let mut hbs = Handlebars::new();
|
let mut hbs = Handlebars::new();
|
||||||
hbs.set_strict_mode(false);
|
hbs.set_strict_mode(false);
|
||||||
hbs.register_escape_fn(|s: &str| s.to_string());
|
hbs.register_escape_fn(|s: &str| s.to_string());
|
||||||
hbs.register_template_string("nsis", template)
|
hbs.register_template_string("template", &safe_template)
|
||||||
.context("Failed to parse NSIS template")?;
|
.context("Failed to parse template")?;
|
||||||
hbs.render("nsis", data)
|
let rendered = hbs
|
||||||
.context("Failed to render NSIS template")
|
.render("template", data)
|
||||||
|
.context("Failed to render template")?;
|
||||||
|
Ok(rendered.replace(BACKSLASH_PLACEHOLDER, "\\"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a semver version string to a WiX-compatible version.
|
/// Convert a semver version string to a WiX-compatible version.
|
||||||
@@ -1015,7 +1014,15 @@ const WIX_TEMPLATE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
|
|||||||
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if license}}
|
||||||
<UIRef Id="WixUI_InstallDir" />
|
<UIRef Id="WixUI_InstallDir" />
|
||||||
|
{{else}}
|
||||||
|
<UI>
|
||||||
|
<UIRef Id="WixUI_InstallDir" />
|
||||||
|
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
|
||||||
|
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
|
||||||
|
</UI>
|
||||||
|
{{/if}}
|
||||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||||
|
|
||||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
@@ -1244,10 +1251,10 @@ SectionEnd
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::WIX_TEMPLATE;
|
use super::{render_template, WIX_TEMPLATE};
|
||||||
use crate::bundler::Arch;
|
use crate::bundler::Arch;
|
||||||
use handlebars::Handlebars;
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wix_program_files_folder_matches_architecture() {
|
fn wix_program_files_folder_matches_architecture() {
|
||||||
@@ -1264,37 +1271,32 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wix_template_uses_arch_specific_program_files_folder() {
|
fn wix_template_uses_arch_specific_program_files_folder() {
|
||||||
let mut hbs = Handlebars::new();
|
let data: BTreeMap<String, serde_json::Value> = serde_json::from_value(json!({
|
||||||
hbs.set_strict_mode(false);
|
"product_name": "Hotdog",
|
||||||
hbs.register_escape_fn(|s: &str| s.to_string());
|
"upgrade_code": "00000000-0000-0000-0000-000000000000",
|
||||||
hbs.register_template_string("wix", WIX_TEMPLATE).unwrap();
|
"version": "0.1.0",
|
||||||
|
"publisher": "Dioxus Labs",
|
||||||
|
"main_binary_name": "hotdog.exe",
|
||||||
|
"main_binary_path": "C:\\staging\\hotdog.exe",
|
||||||
|
"short_description": "Hotdog app",
|
||||||
|
"allow_downgrades": false,
|
||||||
|
"fips_compliant": false,
|
||||||
|
"install_tree": "",
|
||||||
|
"component_refs_xml": "",
|
||||||
|
"component_group_refs": [],
|
||||||
|
"component_refs": [],
|
||||||
|
"feature_group_refs": [],
|
||||||
|
"feature_refs": [],
|
||||||
|
"merge_refs": [],
|
||||||
|
"wix_program_files_folder": "ProgramFiles64Folder"
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let rendered = hbs
|
let rendered = render_template(WIX_TEMPLATE, &data).unwrap();
|
||||||
.render(
|
|
||||||
"wix",
|
|
||||||
&json!({
|
|
||||||
"product_name": "Hotdog",
|
|
||||||
"upgrade_code": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"publisher": "Dioxus Labs",
|
|
||||||
"main_binary_name": "hotdog.exe",
|
|
||||||
"main_binary_path": "C:\\staging\\hotdog.exe",
|
|
||||||
"short_description": "Hotdog app",
|
|
||||||
"allow_downgrades": false,
|
|
||||||
"fips_compliant": false,
|
|
||||||
"install_tree": "",
|
|
||||||
"component_refs_xml": "",
|
|
||||||
"component_group_refs": [],
|
|
||||||
"component_refs": [],
|
|
||||||
"feature_group_refs": [],
|
|
||||||
"feature_refs": [],
|
|
||||||
"merge_refs": [],
|
|
||||||
"wix_program_files_folder": "ProgramFiles64Folder"
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(rendered.contains("<Directory Id=\"ProgramFiles64Folder\">"));
|
assert!(rendered.contains("<Directory Id=\"ProgramFiles64Folder\">"));
|
||||||
assert!(!rendered.contains("<Directory Id=\"ProgramFilesFolder\">"));
|
assert!(!rendered.contains("<Directory Id=\"ProgramFilesFolder\">"));
|
||||||
|
// Verify \{{ substitution works: registry key should contain actual values
|
||||||
|
assert!(rendered.contains("Software\\Dioxus Labs\\Hotdog"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,6 +602,11 @@ pub struct AndroidConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub compile_sdk: Option<u32>,
|
pub compile_sdk: Option<u32>,
|
||||||
|
|
||||||
|
/// Native library name loaded by NativeActivity (without `lib` prefix and `.so` extension).
|
||||||
|
/// Defaults to `"main"` (i.e. `libmain.so`). Override if you need a custom library name.
|
||||||
|
#[serde(default)]
|
||||||
|
pub lib_name: Option<String>,
|
||||||
|
|
||||||
/// Hardware/software features required.
|
/// Hardware/software features required.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub features: Vec<String>,
|
pub features: Vec<String>,
|
||||||
|
|||||||
@@ -428,6 +428,9 @@ impl WasmBindgen {
|
|||||||
fn git_install_url(&self) -> Option<String> {
|
fn git_install_url(&self) -> Option<String> {
|
||||||
let platform = if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
|
let platform = if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
|
||||||
"x86_64-pc-windows-msvc"
|
"x86_64-pc-windows-msvc"
|
||||||
|
} else if cfg!(all(target_os = "windows", target_arch = "aarch64")) {
|
||||||
|
// No native ARM64 binary available; the x86_64 binary runs fine under emulation.
|
||||||
|
"x86_64-pc-windows-msvc"
|
||||||
} else if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
|
} else if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
|
||||||
"x86_64-unknown-linux-musl"
|
"x86_64-unknown-linux-musl"
|
||||||
} else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
|
} else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
|
||||||
|
|||||||
Reference in new issue
Block a user