CLI: configurable Android versionCode and iOS/macOS CFBundleVersion

This commit is contained in:
nico.burns authored and Stephen Waits committed 2026-09-05 20:29:43 -06:00
1 parent 6adf7ba465
commit 07dddd569e
11 files changed
+129 -5

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ dioxus-cli-telemetry = { workspace = true }
dioxus-component-manifest = { workspace = true } dioxus-component-manifest = { workspace = true }
bytes = { workspace = true } bytes = { workspace = true }
clap = { workspace = true, features = ["derive", "cargo"] } clap = { workspace = true, features = ["derive", "cargo", "env"] }
convert_case = { workspace = true } convert_case = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
uuid = { workspace = true, features = ["v4", "v5", "v7"] } uuid = { workspace = true, features = ["v4", "v5", "v7"] }
@@ -13,7 +13,7 @@ android {
applicationId = "{{ application_id }}" applicationId = "{{ application_id }}"
minSdk = {{ min_sdk }} minSdk = {{ min_sdk }}
targetSdk = {{ target_sdk }} targetSdk = {{ target_sdk }}
versionCode = 1 versionCode = {{ version_code }}
versionName = "{{ version }}" versionName = "{{ version }}"
} }
{{#if android_bundle}} {{#if android_bundle}}
+1 -1
View File
@@ -15,7 +15,7 @@
<string>{{ bundle_name }}</string> <string>{{ bundle_name }}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>{{ version }}</string> <string>{{ bundle_version }}</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>{{ version }}</string> <string>{{ version }}</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
+1 -1
View File
@@ -27,7 +27,7 @@
<string>{{ version }}</string> <string>{{ version }}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>{{ version }}</string> <string>{{ bundle_version }}</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>{{ minimum_system_version }}</string> <string>{{ minimum_system_version }}</string>
+16
View File
@@ -267,6 +267,15 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"version_code": {
"description": "The version code used for `versionCode` in the generated Gradle project.\n\nMust be between 1 and 2100000000 (the Google Play Store maximum).\n\nCan be overridden per-build with `--version-code` or the `DX_ANDROID_VERSION_CODE`\nenvironment variable. If unset, it is derived from the crate version as\n`major * 1000000 + minor * 1000 + patch`.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0
} }
} }
}, },
@@ -964,6 +973,13 @@
"type": "string" "type": "string"
} }
}, },
"bundle_version": {
"description": "The bundle version string (CFBundleVersion).\n\nThis is the machine-readable build number that must increase with every upload\nto App Store Connect. Can be overridden per-build with `--bundle-version` or the\n`DX_BUNDLE_VERSION` environment variable. Defaults to the crate version.",
"type": [
"string",
"null"
]
},
"category": { "category": {
"description": "App category. Overrides `bundle.category` for iOS builds.", "description": "App category. Overrides `bundle.category` for iOS builds.",
"type": [ "type": [
+2
View File
@@ -120,6 +120,7 @@ impl BuildRequest {
application_id: String, application_id: String,
app_name: String, app_name: String,
version: String, version: String,
version_code: u32,
android_bundle: Option<crate::AndroidSettings>, android_bundle: Option<crate::AndroidSettings>,
/// Android SDK version settings /// Android SDK version settings
min_sdk: u32, min_sdk: u32,
@@ -180,6 +181,7 @@ impl BuildRequest {
application_id: self.bundle_identifier(), application_id: self.bundle_identifier(),
app_name: self.bundled_app_name(), app_name: self.bundled_app_name(),
version: self.crate_version(), version: self.crate_version(),
version_code: self.android_version_code()?,
android_bundle: self.config.bundle.android.clone(), android_bundle: self.config.bundle.android.clone(),
min_sdk: self.config.android.min_sdk.unwrap_or(24), min_sdk: self.config.android.min_sdk.unwrap_or(24),
target_sdk: self.config.android.target_sdk.unwrap_or(34), target_sdk: self.config.android.target_sdk.unwrap_or(34),
+4
View File
@@ -160,6 +160,8 @@ impl BuildRequest {
pub executable_name: String, pub executable_name: String,
/// App version string (from Cargo.toml) /// App version string (from Cargo.toml)
pub version: String, pub version: String,
/// Bundle version string (CFBundleVersion)
pub bundle_version: String,
/// Permission usage descriptions /// Permission usage descriptions
pub permissions: Vec<PlistPermission>, pub permissions: Vec<PlistPermission>,
/// Additional plist entries as raw XML /// Additional plist entries as raw XML
@@ -231,6 +233,7 @@ impl BuildRequest {
executable_name: self.platform_exe_name(), executable_name: self.platform_exe_name(),
bundle_identifier: self.bundle_identifier(), bundle_identifier: self.bundle_identifier(),
version: self.crate_version(), version: self.crate_version(),
bundle_version: self.apple_bundle_version(),
permissions, permissions,
plist_entries, plist_entries,
raw_plist, raw_plist,
@@ -265,6 +268,7 @@ impl BuildRequest {
executable_name: self.platform_exe_name(), executable_name: self.platform_exe_name(),
bundle_identifier: self.bundle_identifier(), bundle_identifier: self.bundle_identifier(),
version: self.crate_version(), version: self.crate_version(),
bundle_version: self.apple_bundle_version(),
permissions, permissions,
plist_entries, plist_entries,
raw_plist, raw_plist,
+62
View File
@@ -269,6 +269,8 @@ pub(crate) struct BuildRequest {
pub(crate) using_dioxus_explicitly: bool, pub(crate) using_dioxus_explicitly: bool,
pub(crate) apple_entitlements: Option<PathBuf>, pub(crate) apple_entitlements: Option<PathBuf>,
pub(crate) apple_team_id: Option<String>, pub(crate) apple_team_id: Option<String>,
pub(crate) version_code: Option<u32>,
pub(crate) bundle_version: Option<String>,
pub(crate) session_cache_dir: PathBuf, pub(crate) session_cache_dir: PathBuf,
pub(crate) raw_json_diagnostics: bool, pub(crate) raw_json_diagnostics: bool,
pub(crate) windows_subsystem: Option<String>, pub(crate) windows_subsystem: Option<String>,
@@ -905,6 +907,8 @@ impl BuildRequest {
inject_loading_scripts: args.inject_loading_scripts, inject_loading_scripts: args.inject_loading_scripts,
apple_entitlements: args.apple_entitlements.clone(), apple_entitlements: args.apple_entitlements.clone(),
apple_team_id: args.apple_team_id.clone(), apple_team_id: args.apple_team_id.clone(),
version_code: args.version_code,
bundle_version: args.bundle_version.clone(),
raw_json_diagnostics: args.raw_json_diagnostics, raw_json_diagnostics: args.raw_json_diagnostics,
windows_subsystem: args.windows_subsystem.clone(), windows_subsystem: args.windows_subsystem.clone(),
}) })
@@ -2685,6 +2689,64 @@ impl BuildRequest {
.to_string() .to_string()
} }
/// Get the Android `versionCode` for the generated Gradle project.
///
/// Resolution order: `--version-code` / `DX_ANDROID_VERSION_CODE`, then
/// `[android] version_code` in Dioxus.toml, then a value derived from the crate
/// version as `major * 1000000 + minor * 1000 + patch`.
pub(crate) fn android_version_code(&self) -> Result<u32> {
/// The maximum value accepted by the Google Play Store.
const MAX_VERSION_CODE: u32 = 2_100_000_000;
let code = match self.version_code.or(self.config.android.version_code) {
Some(code) => code,
None => {
let version = &self.workspace.krates[self.crate_package].version;
if version.major > 2099 || version.minor > 999 || version.patch > 999 {
bail!(
"Cannot derive an Android versionCode from crate version `{version}`: each component must fit `major <= 2099`, `minor <= 999`, `patch <= 999`. Set an explicit version code via `[android] version_code` in Dioxus.toml or the `--version-code` flag."
);
}
if !version.pre.is_empty() {
tracing::warn!(
"Pre-release identifier of crate version `{version}` is ignored when deriving the Android versionCode"
);
}
let code =
(version.major * 1_000_000 + version.minor * 1_000 + version.patch) as u32;
// Google Play requires versionCode >= 1, so bump 0.0.0 up to 1.
code.max(1)
}
};
if !(1..=MAX_VERSION_CODE).contains(&code) {
bail!(
"Android versionCode must be between 1 and {MAX_VERSION_CODE}, got {code}. See https://developer.android.com/studio/publish/versioning"
);
}
Ok(code)
}
/// The explicitly-configured `CFBundleVersion` for iOS/macOS bundles, if any.
///
/// Resolution order: `--bundle-version` / `DX_BUNDLE_VERSION`, then the platform's
/// `bundle_version` in Dioxus.toml (`[ios]` or `[macos]`).
pub(crate) fn apple_bundle_version_override(&self) -> Option<String> {
self.bundle_version.clone().or_else(|| match self.bundle {
BundleFormat::Ios => self.config.ios.bundle_version.clone(),
BundleFormat::MacOS => self.config.macos.bundle_version.clone(),
_ => None,
})
}
/// The `CFBundleVersion` for iOS/macOS bundles, falling back to the crate version.
pub(crate) fn apple_bundle_version(&self) -> String {
self.apple_bundle_version_override()
.unwrap_or_else(|| self.crate_version())
}
pub(crate) fn bundle_identifier(&self) -> String { pub(crate) fn bundle_identifier(&self) -> String {
use crate::config::BundlePlatform; use crate::config::BundlePlatform;
+5 -1
View File
@@ -482,7 +482,11 @@ impl BundleContext<'_> {
); );
let version = self.version_string(); let version = self.version_string();
let bundle_version = macos_settings.bundle_version.as_deref().unwrap_or(&version); let bundle_version = self
.build
.apple_bundle_version_override()
.or_else(|| macos_settings.bundle_version.clone())
.unwrap_or_else(|| version.clone());
dict.insert( dict.insert(
"CFBundleShortVersionString".into(), "CFBundleShortVersionString".into(),
+18
View File
@@ -162,6 +162,22 @@ pub(crate) struct TargetArgs {
#[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)] #[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)]
pub(crate) wasm_js_cfg: bool, pub(crate) wasm_js_cfg: bool,
/// The Android `versionCode` to stamp into the generated Gradle project.
///
/// Must be between 1 and 2100000000 (the Google Play Store maximum). Overrides
/// `[android] version_code` in Dioxus.toml. If neither is set, the version code is
/// derived from the crate version as `major * 1000000 + minor * 1000 + patch`.
#[clap(long, env = "DX_ANDROID_VERSION_CODE", help_heading = HELP_HEADING)]
pub(crate) version_code: Option<u32>,
/// The bundle version (CFBundleVersion) to stamp into the Info.plist of iOS and macOS bundles.
///
/// This is the machine-readable build number that must increase with every upload to
/// App Store Connect. Overrides `[ios] bundle_version` / `[macos] bundle_version` in
/// Dioxus.toml. Defaults to the crate version.
#[clap(long, env = "DX_BUNDLE_VERSION", help_heading = HELP_HEADING)]
pub(crate) bundle_version: Option<String>,
/// The Windows subsystem to use when building for Windows targets. This can be either `CONSOLE` or `WINDOWS`. /// The Windows subsystem to use when building for Windows targets. This can be either `CONSOLE` or `WINDOWS`.
/// ///
/// By default, DX uses `WINDOWS` since it assumes a GUI application, but you can override this behavior with this flag. /// By default, DX uses `WINDOWS` since it assumes a GUI application, but you can override this behavior with this flag.
@@ -200,6 +216,8 @@ impl Anonymized for TargetArgs {
"debug_symbols": self.debug_symbols, "debug_symbols": self.debug_symbols,
"keep_names": self.keep_names, "keep_names": self.keep_names,
"device": self.device, "device": self.device,
"version_code": self.version_code,
"bundle_version": self.bundle_version.is_some(),
"base_path": self.base_path.is_some(), "base_path": self.base_path.is_some(),
"cargo_args": self.cargo_args.is_some(), "cargo_args": self.cargo_args.is_some(),
"rustc_args": self.rustc_args.is_some(), "rustc_args": self.rustc_args.is_some(),
+18
View File
@@ -326,6 +326,14 @@ pub struct IosConfig {
pub long_description: Option<String>, pub long_description: Option<String>,
// === iOS-specific settings === // === iOS-specific settings ===
/// The bundle version string (CFBundleVersion).
///
/// This is the machine-readable build number that must increase with every upload
/// to App Store Connect. Can be overridden per-build with `--bundle-version` or the
/// `DX_BUNDLE_VERSION` environment variable. Defaults to the crate version.
#[serde(default)]
pub bundle_version: Option<String>,
/// Minimum iOS deployment target (e.g., "15.0"). /// Minimum iOS deployment target (e.g., "15.0").
#[serde(default)] #[serde(default)]
pub deployment_target: Option<String>, pub deployment_target: Option<String>,
@@ -590,6 +598,16 @@ pub struct AndroidConfig {
pub signing: Option<AndroidSigningConfig>, pub signing: Option<AndroidSigningConfig>,
// === Android-specific settings === // === Android-specific settings ===
/// The version code used for `versionCode` in the generated Gradle project.
///
/// Must be between 1 and 2100000000 (the Google Play Store maximum).
///
/// Can be overridden per-build with `--version-code` or the `DX_ANDROID_VERSION_CODE`
/// environment variable. If unset, it is derived from the crate version as
/// `major * 1000000 + minor * 1000 + patch`.
#[serde(default)]
pub version_code: Option<u32>,
/// Minimum SDK version. /// Minimum SDK version.
#[serde(default)] #[serde(default)]
pub min_sdk: Option<u32>, pub min_sdk: Option<u32>,