From 0e3f8352e8730c49567455c155f0c127b749d553 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 11:04:23 +0000 Subject: [PATCH] iOS: drive app version from libs.versions.toml Generate each iOS app's MARKETING_VERSION and CURRENT_PROJECT_VERSION into a Version.xcconfig from appVersionString / appBuildNumber, the same single source of truth Android already uses. Config.xcconfig now #includes the generated file instead of hardcoding the version, so bumping the TOML updates Android and iOS together. A generateIosVersionConfig task per compose module writes the file (only when values change) and runs before embedAndSignAppleFrameworkForXcode; the root updateIosVersions task regenerates all three at once. This aligns iOS with Android at 1.0.41 / 41. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_0173eTCarvivVN859UekPvjw --- build.gradle.kts | 12 +++++++ composeJournalsApp/build.gradle.kts | 36 +++++++++++++++++++ composeNotesApp/build.gradle.kts | 36 +++++++++++++++++++ composeTasksApp/build.gradle.kts | 36 +++++++++++++++++++ .../Configuration/Config.xcconfig | 6 ++-- .../Configuration/Version.xcconfig | 5 +++ .../iosNotesApp/Configuration/Config.xcconfig | 6 ++-- .../Configuration/Version.xcconfig | 5 +++ .../iosTasksApp/Configuration/Config.xcconfig | 6 ++-- .../Configuration/Version.xcconfig | 5 +++ 10 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 iosApp/iosJournalsApp/Configuration/Version.xcconfig create mode 100644 iosApp/iosNotesApp/Configuration/Version.xcconfig create mode 100644 iosApp/iosTasksApp/Configuration/Version.xcconfig diff --git a/build.gradle.kts b/build.gradle.kts index 7e5dddda..3e34fa73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,4 +11,16 @@ plugins { alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.ktor) apply false alias(libs.plugins.aboutLibraries) apply false +} + +// Regenerate all three iOS Version.xcconfig files from gradle/libs.versions.toml. +// Run this after bumping appVersionString / appBuildNumber, then commit the result. +tasks.register("updateIosVersions") { + group = "versioning" + description = "Regenerate iosApp/*/Configuration/Version.xcconfig from libs.versions.toml" + dependsOn( + ":composeJournalsApp:generateIosVersionConfig", + ":composeNotesApp:generateIosVersionConfig", + ":composeTasksApp:generateIosVersionConfig", + ) } \ No newline at end of file diff --git a/composeJournalsApp/build.gradle.kts b/composeJournalsApp/build.gradle.kts index 8c975f85..476cafba 100644 --- a/composeJournalsApp/build.gradle.kts +++ b/composeJournalsApp/build.gradle.kts @@ -156,3 +156,39 @@ compose.desktop { } } } + +// --- iOS app versioning --------------------------------------------------- +// Single source of truth for the version lives in gradle/libs.versions.toml, the +// same values Android reads. This task writes them into the iOS xcconfig so the +// iOS app version stays in lockstep: +// MARKETING_VERSION <- appVersionString (-> CFBundleShortVersionString) +// CURRENT_PROJECT_VERSION <- appBuildNumber (-> CFBundleVersion) +// The generated file is #included by iosApp/iosJournalsApp/Configuration/Config.xcconfig. +// It's rewritten only when the values change, so normal builds don't dirty git. +val generateIosVersionConfig by tasks.registering { + val versionName = libs.versions.appVersionString.get() + val buildNumber = libs.versions.appBuildNumber.get() + val xcconfig = rootProject.file("iosApp/iosJournalsApp/Configuration/Version.xcconfig") + inputs.property("versionName", versionName) + inputs.property("buildNumber", buildNumber) + outputs.file(xcconfig) + doLast { + val content = buildString { + appendLine("// Generated from gradle/libs.versions.toml - do not edit by hand.") + appendLine("// Bump appVersionString / appBuildNumber there instead, then rebuild") + appendLine("// (or run ./gradlew :composeJournalsApp:generateIosVersionConfig).") + appendLine("MARKETING_VERSION = $versionName") + appendLine("CURRENT_PROJECT_VERSION = $buildNumber") + } + if (!xcconfig.exists() || xcconfig.readText() != content) { + xcconfig.parentFile.mkdirs() + xcconfig.writeText(content) + } + } +} + +// Xcode's "Compile Kotlin Framework" build phase runs embedAndSignAppleFrameworkForXcode, +// so hooking in here keeps Version.xcconfig fresh on every iOS build. +tasks.matching { it.name == "embedAndSignAppleFrameworkForXcode" }.configureEach { + dependsOn(generateIosVersionConfig) +} diff --git a/composeNotesApp/build.gradle.kts b/composeNotesApp/build.gradle.kts index e8b3358a..945f1aff 100644 --- a/composeNotesApp/build.gradle.kts +++ b/composeNotesApp/build.gradle.kts @@ -156,3 +156,39 @@ compose.desktop { } } } + +// --- iOS app versioning --------------------------------------------------- +// Single source of truth for the version lives in gradle/libs.versions.toml, the +// same values Android reads. This task writes them into the iOS xcconfig so the +// iOS app version stays in lockstep: +// MARKETING_VERSION <- appVersionString (-> CFBundleShortVersionString) +// CURRENT_PROJECT_VERSION <- appBuildNumber (-> CFBundleVersion) +// The generated file is #included by iosApp/iosNotesApp/Configuration/Config.xcconfig. +// It's rewritten only when the values change, so normal builds don't dirty git. +val generateIosVersionConfig by tasks.registering { + val versionName = libs.versions.appVersionString.get() + val buildNumber = libs.versions.appBuildNumber.get() + val xcconfig = rootProject.file("iosApp/iosNotesApp/Configuration/Version.xcconfig") + inputs.property("versionName", versionName) + inputs.property("buildNumber", buildNumber) + outputs.file(xcconfig) + doLast { + val content = buildString { + appendLine("// Generated from gradle/libs.versions.toml - do not edit by hand.") + appendLine("// Bump appVersionString / appBuildNumber there instead, then rebuild") + appendLine("// (or run ./gradlew :composeNotesApp:generateIosVersionConfig).") + appendLine("MARKETING_VERSION = $versionName") + appendLine("CURRENT_PROJECT_VERSION = $buildNumber") + } + if (!xcconfig.exists() || xcconfig.readText() != content) { + xcconfig.parentFile.mkdirs() + xcconfig.writeText(content) + } + } +} + +// Xcode's "Compile Kotlin Framework" build phase runs embedAndSignAppleFrameworkForXcode, +// so hooking in here keeps Version.xcconfig fresh on every iOS build. +tasks.matching { it.name == "embedAndSignAppleFrameworkForXcode" }.configureEach { + dependsOn(generateIosVersionConfig) +} diff --git a/composeTasksApp/build.gradle.kts b/composeTasksApp/build.gradle.kts index 5c2f1a35..37031a07 100644 --- a/composeTasksApp/build.gradle.kts +++ b/composeTasksApp/build.gradle.kts @@ -157,3 +157,39 @@ compose.desktop { } } } + +// --- iOS app versioning --------------------------------------------------- +// Single source of truth for the version lives in gradle/libs.versions.toml, the +// same values Android reads. This task writes them into the iOS xcconfig so the +// iOS app version stays in lockstep: +// MARKETING_VERSION <- appVersionString (-> CFBundleShortVersionString) +// CURRENT_PROJECT_VERSION <- appBuildNumber (-> CFBundleVersion) +// The generated file is #included by iosApp/iosTasksApp/Configuration/Config.xcconfig. +// It's rewritten only when the values change, so normal builds don't dirty git. +val generateIosVersionConfig by tasks.registering { + val versionName = libs.versions.appVersionString.get() + val buildNumber = libs.versions.appBuildNumber.get() + val xcconfig = rootProject.file("iosApp/iosTasksApp/Configuration/Version.xcconfig") + inputs.property("versionName", versionName) + inputs.property("buildNumber", buildNumber) + outputs.file(xcconfig) + doLast { + val content = buildString { + appendLine("// Generated from gradle/libs.versions.toml - do not edit by hand.") + appendLine("// Bump appVersionString / appBuildNumber there instead, then rebuild") + appendLine("// (or run ./gradlew :composeTasksApp:generateIosVersionConfig).") + appendLine("MARKETING_VERSION = $versionName") + appendLine("CURRENT_PROJECT_VERSION = $buildNumber") + } + if (!xcconfig.exists() || xcconfig.readText() != content) { + xcconfig.parentFile.mkdirs() + xcconfig.writeText(content) + } + } +} + +// Xcode's "Compile Kotlin Framework" build phase runs embedAndSignAppleFrameworkForXcode, +// so hooking in here keeps Version.xcconfig fresh on every iOS build. +tasks.matching { it.name == "embedAndSignAppleFrameworkForXcode" }.configureEach { + dependsOn(generateIosVersionConfig) +} diff --git a/iosApp/iosJournalsApp/Configuration/Config.xcconfig b/iosApp/iosJournalsApp/Configuration/Config.xcconfig index 788fa19b..1d252d5e 100644 --- a/iosApp/iosJournalsApp/Configuration/Config.xcconfig +++ b/iosApp/iosJournalsApp/Configuration/Config.xcconfig @@ -1,7 +1,9 @@ +#include "Version.xcconfig" + DEVELOPMENT_TEAM=BZY8AT5K2W PRODUCT_NAME=spectacledJournals PRODUCT_BUNDLE_IDENTIFIER=at.techbee.spectacled.journals -CURRENT_PROJECT_VERSION=34 -MARKETING_VERSION=1.0 +// MARKETING_VERSION and CURRENT_PROJECT_VERSION come from Version.xcconfig, which is +// generated from gradle/libs.versions.toml. Do not set them here. diff --git a/iosApp/iosJournalsApp/Configuration/Version.xcconfig b/iosApp/iosJournalsApp/Configuration/Version.xcconfig new file mode 100644 index 00000000..6072b7f8 --- /dev/null +++ b/iosApp/iosJournalsApp/Configuration/Version.xcconfig @@ -0,0 +1,5 @@ +// Generated from gradle/libs.versions.toml - do not edit by hand. +// Bump appVersionString / appBuildNumber there instead, then rebuild +// (or run ./gradlew :composeJournalsApp:generateIosVersionConfig). +MARKETING_VERSION = 1.0.41 +CURRENT_PROJECT_VERSION = 41 diff --git a/iosApp/iosNotesApp/Configuration/Config.xcconfig b/iosApp/iosNotesApp/Configuration/Config.xcconfig index 0ea1545a..1255199b 100644 --- a/iosApp/iosNotesApp/Configuration/Config.xcconfig +++ b/iosApp/iosNotesApp/Configuration/Config.xcconfig @@ -1,7 +1,9 @@ +#include "Version.xcconfig" + DEVELOPMENT_TEAM=BZY8AT5K2W PRODUCT_NAME=spectacledNotes PRODUCT_BUNDLE_IDENTIFIER=at.techbee.spectacled.notes -CURRENT_PROJECT_VERSION=34 -MARKETING_VERSION=1.0 +// MARKETING_VERSION and CURRENT_PROJECT_VERSION come from Version.xcconfig, which is +// generated from gradle/libs.versions.toml. Do not set them here. diff --git a/iosApp/iosNotesApp/Configuration/Version.xcconfig b/iosApp/iosNotesApp/Configuration/Version.xcconfig new file mode 100644 index 00000000..a4d0626d --- /dev/null +++ b/iosApp/iosNotesApp/Configuration/Version.xcconfig @@ -0,0 +1,5 @@ +// Generated from gradle/libs.versions.toml - do not edit by hand. +// Bump appVersionString / appBuildNumber there instead, then rebuild +// (or run ./gradlew :composeNotesApp:generateIosVersionConfig). +MARKETING_VERSION = 1.0.41 +CURRENT_PROJECT_VERSION = 41 diff --git a/iosApp/iosTasksApp/Configuration/Config.xcconfig b/iosApp/iosTasksApp/Configuration/Config.xcconfig index 5de94d9a..12dd4f01 100644 --- a/iosApp/iosTasksApp/Configuration/Config.xcconfig +++ b/iosApp/iosTasksApp/Configuration/Config.xcconfig @@ -1,7 +1,9 @@ +#include "Version.xcconfig" + DEVELOPMENT_TEAM=BZY8AT5K2W PRODUCT_NAME=spectacledTasks PRODUCT_BUNDLE_IDENTIFIER=at.techbee.spectacled.tasks -CURRENT_PROJECT_VERSION=34 -MARKETING_VERSION=1.0 +// MARKETING_VERSION and CURRENT_PROJECT_VERSION come from Version.xcconfig, which is +// generated from gradle/libs.versions.toml. Do not set them here. diff --git a/iosApp/iosTasksApp/Configuration/Version.xcconfig b/iosApp/iosTasksApp/Configuration/Version.xcconfig new file mode 100644 index 00000000..da018844 --- /dev/null +++ b/iosApp/iosTasksApp/Configuration/Version.xcconfig @@ -0,0 +1,5 @@ +// Generated from gradle/libs.versions.toml - do not edit by hand. +// Bump appVersionString / appBuildNumber there instead, then rebuild +// (or run ./gradlew :composeTasksApp:generateIosVersionConfig). +MARKETING_VERSION = 1.0.41 +CURRENT_PROJECT_VERSION = 41