From 5cb29988d8ebb4c9625af6452f7931d0e68d1cb7 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 2 Nov 2026 11:09:19 +0100 Subject: build: update to Gradle 9.8.2, AGP 9.5.0, Kotlin 2.4.21, Hilt 2.60.1 AGP 9 migration: - Configure the shared Android setup through the new DSL (CommonExtension, ApplicationExtension) instead of the removed TestedExtension and BaseAppModuleExtension; targetSdk moves to the application extension. - Stop applying the Kotlin Android plugin; AGP 9 compiles Kotlin itself. - Replace kapt with KSP 2.3.13 for Hilt, which built-in Kotlin requires, and carry the Dagger compiler options over as KSP arguments. - AGP 9 now applies packaging excludes to unit-test resources, which stripped the diceware word list (**/*.txt) from its tests; lift that exclude in the diceware module. - Use resources.directories instead of the deprecated srcDir. Plugin updates required by Gradle 9 / AGP 9: Sentry Gradle plugin 6.25.0 (sentry-android 8.62.0), metalava plugin 0.5.1, maven-publish 0.37.0, spotless 8.10.4, version-catalog-update 1.1.2, download task 5.7.0, foojay resolver 1.0.0, with their API changes. Newer metalava models Kotlin properties, so autofill-parser/api.txt is regenerated; the diff is representation only, no API was added or removed. Fix new Kotlin 2.4 and lint findings (explicit return type in a function with an early return, UseKtx in autofill-parser), drop deprecated Kotlin Gradle properties, and make the AGENTS.md pre-commit command cover every task CI runs (both flavors, lint on all modules). Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 9 ++- app/build.gradle.kts | 12 ++- .../viewmodel/SearchableRepositoryViewModel.kt | 2 +- autofill-parser/api.txt | 74 ++++++++---------- autofill-parser/build.gradle.kts | 2 +- .../autofillparser/AutofillFormParser.kt | 4 +- .../autofillparser/FeatureAndTrustDetection.kt | 4 +- build-logic/build.gradle.kts | 4 - build-logic/settings.gradle.kts | 2 +- .../app/passwordstore/gradle/AndroidCommon.kt | 37 ++++----- .../gradle/DependencyUpdatesPlugin.kt | 1 - .../passwordstore/gradle/KotlinAndroidPlugin.kt | 7 +- .../app/passwordstore/gradle/KotlinKaptPlugin.kt | 51 ------------ .../gradle/PublishedAndroidLibraryPlugin.kt | 7 +- .../app/passwordstore/gradle/signing/AppSigning.kt | 6 +- gradle.properties | 9 --- gradle/libs.versions.toml | 21 ++--- gradle/wrapper/gradle-wrapper.jar | Bin 44309 -> 48749 bytes gradle/wrapper/gradle-wrapper.properties | 6 +- gradlew | 11 +-- gradlew.bat | 86 +++++++++++++-------- passgen/diceware/build.gradle.kts | 4 +- settings.gradle.kts | 2 +- 23 files changed, 153 insertions(+), 208 deletions(-) delete mode 100644 build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinKaptPlugin.kt diff --git a/AGENTS.md b/AGENTS.md index 3fa60b0c..e1ed9d1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,13 +33,13 @@ Needs JDK 17 and the Android SDK. `local.properties` (gitignored) holds `sdk.dir ./gradlew assembleFreeDebug # debug APK ./gradlew test -PslimTests # unit tests, as CI runs them ./gradlew spotlessApply # formatting (ktfmt); CI runs spotlessCheck - ./gradlew lintFreeRelease + ./gradlew lint # all modules and variants, as CI runs it Prefix with `CI=1` to match CI: it turns Kotlin warnings into errors, so a deprecation warning that passes locally fails CI. Before committing, run: CI=1 ./gradlew spotlessCheck test -PslimTests assembleFreeDebug assembleFreeRelease \ - lintFreeRelease metalavaCheckCompatibilityRelease + assembleNonFreeDebug assembleNonFreeRelease lint metalavaCheckCompatibilityRelease Flavors: `free` (no proprietary dependencies, Sentry replaced by `sentry-stub`) and `nonFree` (adds Sentry and Play Services SMS OTP). @@ -63,6 +63,11 @@ itself, so it cannot be referenced with a local `./` path. and hash included. A dependency bump that moves a baselined jar needs the path updated. - `LaunchActivity` is exported and forwards `FILE_PATH` to `DecryptActivity` (not exported) only after `isFileInRepo` checks it canonically resolves inside the store. +- AGP 9 applies packaging excludes (`AndroidCommon`, e.g. `**/*.txt`) to unit-test java + resources as well. A module whose tests load such a file must remove the exclude locally + (see `passgen/diceware/build.gradle.kts`). +- Hilt runs through KSP (no kapt; AGP 9's built-in Kotlin does not support it). Dagger compiler + options are `ksp { arg(...) }` in `app/build.gradle.kts`. - JGit is pinned at 4.5.7 (2019). Newer JGit needs Java 11 APIs Android only partly desugars; upgrading it is a large, separate job. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 206a3972..bff7d73a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,11 +7,11 @@ plugins { id("com.github.android-password-store.android-application") id("com.github.android-password-store.kotlin-android") - id("com.github.android-password-store.kotlin-kapt") id("com.github.android-password-store.versioning-plugin") id("com.github.android-password-store.sentry") id("com.github.android-password-store.rename-artifacts") alias(libs.plugins.hilt) + alias(libs.plugins.ksp) alias(libs.plugins.kotlin.composeCompiler) } @@ -35,9 +35,17 @@ android { } } +ksp { + // https://dagger.dev/dev-guide/compiler-options + arg("dagger.fastInit", "enabled") + arg("dagger.experimentalDaggerErrorMessages", "enabled") + arg("dagger.hilt.shareTestComponents", "true") + arg("dagger.moduleBindingValidation", "ERROR") +} + dependencies { implementation(platform(libs.compose.bom)) - kapt(libs.dagger.hilt.compiler) + ksp(libs.dagger.hilt.compiler) implementation(libs.androidx.annotation) coreLibraryDesugaring(libs.android.desugarJdkLibs) implementation(projects.autofillParser) diff --git a/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt b/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt index 2bd75aba..48804e7a 100644 --- a/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt @@ -268,7 +268,7 @@ constructor( } .flowOn(dispatcherProvider.io()) - private fun shouldTake(file: File) = + private fun shouldTake(file: File): Boolean = with(file) { if (showHiddenContents) { return !file.name.startsWith(".git") diff --git a/autofill-parser/api.txt b/autofill-parser/api.txt index a999127f..89cfaa3c 100644 --- a/autofill-parser/api.txt +++ b/autofill-parser/api.txt @@ -2,8 +2,6 @@ package com.github.androidpasswordstore.autofillparser { public enum AutofillAction { - method public static com.github.androidpasswordstore.autofillparser.AutofillAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException; - method public static com.github.androidpasswordstore.autofillparser.AutofillAction[] values(); enum_constant public static final com.github.androidpasswordstore.autofillparser.AutofillAction FillOtpFromSms; enum_constant public static final com.github.androidpasswordstore.autofillparser.AutofillAction Generate; enum_constant public static final com.github.androidpasswordstore.autofillparser.AutofillAction Match; @@ -14,13 +12,13 @@ package com.github.androidpasswordstore.autofillparser { method public static String computeCertificatesHash(android.content.Context context, String appPackage); } - @RequiresApi(android.os.Build.VERSION_CODES.O) public abstract sealed class AutofillScenario { - method public final java.util.List getFieldsToSave(); - method public final boolean getHasFieldsToSave(); - method public final boolean getHasPasswordFieldsToSave(); - method public final boolean getHasUsername(); - method public abstract java.util.List getPasswordFieldsToSave(); - method public abstract T? getUsername(); + @RequiresApi(android.os.Build.VERSION_CODES.O) public abstract sealed nonexhaustive class AutofillScenario { + method @InaccessibleFromKotlin public final java.util.List getFieldsToSave(); + method @InaccessibleFromKotlin public final boolean getHasFieldsToSave(); + method @InaccessibleFromKotlin public final boolean getHasPasswordFieldsToSave(); + method @InaccessibleFromKotlin public final boolean getHasUsername(); + method @InaccessibleFromKotlin public abstract java.util.List getPasswordFieldsToSave(); + method @InaccessibleFromKotlin public abstract T? getUsername(); method public final boolean hasFieldsToFillOn(com.github.androidpasswordstore.autofillparser.AutofillAction action); property public final java.util.List fieldsToSave; property public final boolean hasFieldsToSave; @@ -37,15 +35,16 @@ package com.github.androidpasswordstore.autofillparser { } public final class AutofillScenarioKt { - method @RequiresApi(android.os.Build.VERSION_CODES.O) public static void fillWithAutofillId(android.service.autofill.Dataset.Builder, com.github.androidpasswordstore.autofillparser.AutofillScenario scenario, com.github.androidpasswordstore.autofillparser.AutofillAction action, com.github.androidpasswordstore.autofillparser.Credentials? credentials); - method @RequiresApi(android.os.Build.VERSION_CODES.O) public static String? getPasswordValue(com.github.androidpasswordstore.autofillparser.AutofillScenario); - method @RequiresApi(android.os.Build.VERSION_CODES.O) public static String? getUsernameValue(com.github.androidpasswordstore.autofillparser.AutofillScenario); + method @KotlinOnly @RequiresApi(android.os.Build.VERSION_CODES.O) public static void fillWith(android.service.autofill.Dataset.Builder, com.github.androidpasswordstore.autofillparser.AutofillScenario scenario, com.github.androidpasswordstore.autofillparser.AutofillAction action, com.github.androidpasswordstore.autofillparser.Credentials? credentials); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.O) public static void fillWithAutofillId(android.service.autofill.Dataset.Builder, com.github.androidpasswordstore.autofillparser.AutofillScenario scenario, com.github.androidpasswordstore.autofillparser.AutofillAction action, com.github.androidpasswordstore.autofillparser.Credentials? credentials); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.O) public static String? getPasswordValue(com.github.androidpasswordstore.autofillparser.AutofillScenario); + method @InaccessibleFromKotlin @RequiresApi(android.os.Build.VERSION_CODES.O) public static String? getUsernameValue(com.github.androidpasswordstore.autofillparser.AutofillScenario); method @RequiresApi(android.os.Build.VERSION_CODES.O) public static com.github.androidpasswordstore.autofillparser.AutofillScenario? recoverNodes(com.github.androidpasswordstore.autofillparser.AutofillScenario, android.app.assist.AssistStructure structure); + property @RequiresApi(android.os.Build.VERSION_CODES.O) public static String? com.github.androidpasswordstore.autofillparser.AutofillScenario.passwordValue; + property @RequiresApi(android.os.Build.VERSION_CODES.O) public static String? com.github.androidpasswordstore.autofillparser.AutofillScenario.usernameValue; } public enum BrowserAutofillSupportLevel { - method public static com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException; - method public static com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel[] values(); enum_constant public static final com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel FlakyFill; enum_constant public static final com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel GeneralFill; enum_constant public static final com.github.androidpasswordstore.autofillparser.BrowserAutofillSupportLevel GeneralFillAndSave; @@ -59,13 +58,13 @@ package com.github.androidpasswordstore.autofillparser { method public String? component1(); method public String? component2(); method public String? component3(); - method public com.github.androidpasswordstore.autofillparser.Credentials copy(String? username, String? password, String? otp); - method public String? getOtp(); - method public String? getPassword(); - method public String? getUsername(); - property public final String? otp; - property public final String? password; - property public final String? username; + method public com.github.androidpasswordstore.autofillparser.Credentials copy(optional String? username, optional String? password, optional String? otp); + method @InaccessibleFromKotlin public String? getOtp(); + method @InaccessibleFromKotlin public String? getPassword(); + method @InaccessibleFromKotlin public String? getUsername(); + property public String? otp; + property public String? password; + property public String? username; } public final class FeatureAndTrustDetectionKt { @@ -73,15 +72,15 @@ package com.github.androidpasswordstore.autofillparser { } @RequiresApi(android.os.Build.VERSION_CODES.O) public final class FillableForm { - method public com.github.androidpasswordstore.autofillparser.FormOrigin getFormOrigin(); - method public java.util.List getIgnoredIds(); - method public Integer? getSaveFlags(); - method public com.github.androidpasswordstore.autofillparser.AutofillScenario getScenario(); + method @InaccessibleFromKotlin public com.github.androidpasswordstore.autofillparser.FormOrigin getFormOrigin(); + method @InaccessibleFromKotlin public java.util.List getIgnoredIds(); + method @InaccessibleFromKotlin public Integer? getSaveFlags(); + method @InaccessibleFromKotlin public com.github.androidpasswordstore.autofillparser.AutofillScenario getScenario(); method public android.os.Bundle toClientState(); - property public final com.github.androidpasswordstore.autofillparser.FormOrigin formOrigin; - property public final java.util.List ignoredIds; - property public final Integer? saveFlags; - property public final com.github.androidpasswordstore.autofillparser.AutofillScenario scenario; + property public com.github.androidpasswordstore.autofillparser.FormOrigin formOrigin; + property public java.util.List ignoredIds; + property public Integer? saveFlags; + property public com.github.androidpasswordstore.autofillparser.AutofillScenario scenario; field public static final com.github.androidpasswordstore.autofillparser.FillableForm.Companion Companion; } @@ -95,8 +94,8 @@ package com.github.androidpasswordstore.autofillparser { method public void onSuccess(android.content.IntentSender intentSender); } - public abstract sealed class FormOrigin { - method public String getIdentifier(); + public abstract sealed exhaustive class FormOrigin { + method @InaccessibleFromKotlin public String getIdentifier(); method public final String getPrettyIdentifier(android.content.Context context, optional boolean untrusted); method public final android.os.Bundle toBundle(); property public String identifier; @@ -106,7 +105,7 @@ package com.github.androidpasswordstore.autofillparser { public static final class FormOrigin.App extends com.github.androidpasswordstore.autofillparser.FormOrigin { ctor public FormOrigin.App(String identifier); method public String component1(); - method public com.github.androidpasswordstore.autofillparser.FormOrigin.App copy(String identifier); + method public com.github.androidpasswordstore.autofillparser.FormOrigin.App copy(optional String identifier); property public String identifier; } @@ -117,7 +116,7 @@ package com.github.androidpasswordstore.autofillparser { public static final class FormOrigin.Web extends com.github.androidpasswordstore.autofillparser.FormOrigin { ctor public FormOrigin.Web(String identifier); method public String component1(); - method public com.github.androidpasswordstore.autofillparser.FormOrigin.Web copy(String identifier); + method public com.github.androidpasswordstore.autofillparser.FormOrigin.Web copy(optional String identifier); property public String identifier; } @@ -127,12 +126,3 @@ package com.github.androidpasswordstore.autofillparser { } -package mozilla.components.lib.publicsuffixlist { - - public final class PublicSuffixListLoaderTest { - ctor public PublicSuffixListLoaderTest(); - method public void testLoadingBundledPublicSuffixList(); - } - -} - diff --git a/autofill-parser/build.gradle.kts b/autofill-parser/build.gradle.kts index 472f78fb..39157b37 100644 --- a/autofill-parser/build.gradle.kts +++ b/autofill-parser/build.gradle.kts @@ -15,7 +15,7 @@ android { minSdk = 23 consumerProguardFiles("consumer-rules.pro") } - sourceSets { getByName("test") { resources.srcDir("src/main/assets") } } + sourceSets { getByName("test") { resources.directories.add("src/main/assets") } } namespace = "com.github.androidpasswordstore.autofillparser" } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt index c273bdfc..3dc74f33 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt @@ -8,11 +8,11 @@ import android.app.assist.AssistStructure import android.content.Context import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags -import android.net.Uri import android.os.Build import android.os.Bundle import android.view.autofill.AutofillId import androidx.annotation.RequiresApi +import androidx.core.net.toUri import logcat.logcat /** @@ -150,7 +150,7 @@ private class AutofillFormParser( } private fun webOriginToFormOrigin(context: Context, origin: String): FormOrigin? { - val uri = Uri.parse(origin) ?: return null + val uri = origin.toUri() val scheme = uri.scheme ?: return null if (scheme !in SUPPORTED_SCHEMES) return null val host = uri.host ?: return null diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt index 2da3a102..d531dd49 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt @@ -8,11 +8,11 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageManager.ResolveInfoFlags -import android.net.Uri import android.os.Build import android.provider.Settings import android.service.autofill.SaveInfo import androidx.annotation.RequiresApi +import androidx.core.net.toUri /** * In order to add a new browser, do the following: @@ -244,7 +244,7 @@ private fun getBrowserAutofillSupportLevel( public fun getInstalledBrowsersWithAutofillSupportLevel( context: Context ): List> { - val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("https://example.org") } + val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = "https://example.org".toUri() } val installedBrowsers = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.packageManager.queryIntentActivities( diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 2bb5b6ea..dd9c0c8b 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -29,10 +29,6 @@ gradlePlugin { id = "com.github.android-password-store.kotlin-common" implementationClass = "app.passwordstore.gradle.KotlinCommonPlugin" } - register("kotlin-kapt") { - id = "com.github.android-password-store.kotlin-kapt" - implementationClass = "app.passwordstore.gradle.KotlinKaptPlugin" - } register("kotlin-jvm-library") { id = "com.github.android-password-store.kotlin-jvm-library" implementationClass = "app.passwordstore.gradle.KotlinJVMLibrary" diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 1488630b..ea43de31 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -6,7 +6,7 @@ rootProject.name = "build-logic" -plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } +plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } dependencyResolutionManagement { repositories { diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/AndroidCommon.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/AndroidCommon.kt index a26faea9..94b9c112 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/AndroidCommon.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/AndroidCommon.kt @@ -8,37 +8,29 @@ package app.passwordstore.gradle import app.passwordstore.gradle.LintConfig.configureLint import app.passwordstore.gradle.flavors.configureSlimTests import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.CommonExtension import com.android.build.api.dsl.LibraryExtension -import com.android.build.gradle.TestedExtension import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project import org.gradle.api.tasks.testing.Test -import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.withType object AndroidCommon { fun configure(project: Project) { - project.extensions.configure { - compileSdkVersion(35) - defaultConfig { - minSdk = 26 - targetSdk = 34 - } + project.extensions.getByType(CommonExtension::class.java).apply { + compileSdk = 35 + defaultConfig.minSdk = 26 - packagingOptions { - resources.excludes.add("**/*.version") - resources.excludes.add("**/*.txt") - resources.excludes.add("**/*.kotlin_module") - resources.excludes.add("**/plugin.properties") - resources.excludes.add("**/META-INF/AL2.0") - resources.excludes.add("**/META-INF/LGPL2.1") - } + packaging.resources.excludes.add("**/*.version") + packaging.resources.excludes.add("**/*.txt") + packaging.resources.excludes.add("**/*.kotlin_module") + packaging.resources.excludes.add("**/plugin.properties") + packaging.resources.excludes.add("**/META-INF/AL2.0") + packaging.resources.excludes.add("**/META-INF/LGPL2.1") - testOptions { - animationsDisabled = true - unitTests.isReturnDefaultValues = true - } + testOptions.animationsDisabled = true + testOptions.unitTests.isReturnDefaultValues = true project.tasks.withType().configureEach { jvmArgs( @@ -49,7 +41,10 @@ object AndroidCommon { project.configureSlimTests() } - project.extensions.findByType()?.run { lint.configureLint(project) } + project.extensions.findByType()?.run { + defaultConfig.targetSdk = 34 + lint.configureLint(project) + } project.extensions.findByType()?.run { lint.configureLint(project) } val libs = project.extensions.getByName("libs") as LibrariesForLibs project.dependencies.apply { diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/DependencyUpdatesPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/DependencyUpdatesPlugin.kt index 8dbe586f..e35a1843 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/DependencyUpdatesPlugin.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/DependencyUpdatesPlugin.kt @@ -19,7 +19,6 @@ class DependencyUpdatesPlugin : Plugin { override fun apply(project: Project) { project.pluginManager.apply(VersionCatalogUpdatePlugin::class) project.extensions.configure { - keep.keepUnusedLibraries.set(true) versionSelector { val currentVersion = Version.tryParse(it.currentVersion).getOrNull() val newVersion = Version.tryParse(it.candidate.version).getOrNull() diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinAndroidPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinAndroidPlugin.kt index 67fef255..d679abd3 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinAndroidPlugin.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinAndroidPlugin.kt @@ -11,16 +11,13 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.getByType import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension -import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper @Suppress("Unused") class KotlinAndroidPlugin : Plugin { override fun apply(project: Project) { - project.pluginManager.run { - apply(KotlinAndroidPluginWrapper::class) - apply(KotlinCommonPlugin::class) - } + // AGP 9 compiles Kotlin itself (built-in Kotlin); only the shared compiler options remain. + project.pluginManager.apply(KotlinCommonPlugin::class) project.extensions.getByType().jvmToolchain(JVM_TOOLCHAIN_ACTION) } } diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinKaptPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinKaptPlugin.kt deleted file mode 100644 index c4d6467a..00000000 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/KotlinKaptPlugin.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package app.passwordstore.gradle - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.configure -import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin -import org.jetbrains.kotlin.gradle.plugin.KaptExtension - -@Suppress("Unused") -class KotlinKaptPlugin : Plugin { - - override fun apply(project: Project) { - project.pluginManager.apply(Kapt3GradleSubplugin::class) - project.afterEvaluate { - project.extensions.configure { - javacOptions { - if (hasDaggerCompilerDependency()) { - // https://dagger.dev/dev-guide/compiler-options#fastinit-mode - option("-Adagger.fastInit=enabled") - // Enable the better, experimental error messages - // https://github.com/google/dagger/commit/0d2505a727b54f47b8677f42dd4fc5c1924e37f5 - option("-Adagger.experimentalDaggerErrorMessages=enabled") - // Share test components for when we start leveraging Hilt for tests - // https://github.com/google/dagger/releases/tag/dagger-2.34 - option("-Adagger.hilt.shareTestComponents=true") - // KAPT nests errors causing real issues to be suppressed in CI logs - option("-Xmaxerrs", "500") - // Enables per-module validation for faster error detection - // https://github.com/google/dagger/commit/325b516ac6a53d3fc973d247b5231fafda9870a2 - option("-Adagger.moduleBindingValidation=ERROR") - } - } - } - } - project.tasks - .matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") } - .configureEach { enabled = false } - } - - private fun Project.hasDaggerCompilerDependency(): Boolean { - return configurations.any { - it.dependencies.any { dependency -> dependency.name == "hilt-compiler" } - } - } -} diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/PublishedAndroidLibraryPlugin.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/PublishedAndroidLibraryPlugin.kt index 4e1ab741..055a921a 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/PublishedAndroidLibraryPlugin.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/PublishedAndroidLibraryPlugin.kt @@ -10,8 +10,6 @@ package app.passwordstore.gradle import com.vanniktech.maven.publish.AndroidMultiVariantLibrary import com.vanniktech.maven.publish.MavenPublishBaseExtension import com.vanniktech.maven.publish.MavenPublishBasePlugin -import com.vanniktech.maven.publish.SonatypeHost -import me.tylerbwong.gradle.metalava.Documentation import me.tylerbwong.gradle.metalava.extension.MetalavaExtension import org.gradle.api.Plugin import org.gradle.api.Project @@ -28,7 +26,7 @@ class PublishedAndroidLibraryPlugin : Plugin { apply("me.tylerbwong.gradle.metalava") } project.extensions.configure { - publishToMavenCentral(SonatypeHost.DEFAULT, true) + publishToMavenCentral(automaticRelease = true) if (project.providers.environmentVariable("CI").isPresent) { signAllPublications() } @@ -36,10 +34,7 @@ class PublishedAndroidLibraryPlugin : Plugin { pomFromGradleProperties() } project.extensions.configure { - documentation.set(Documentation.PUBLIC) inputKotlinNulls.set(true) - outputKotlinNulls.set(true) - reportLintsAsErrors.set(true) reportWarningsAsErrors.set(true) } } diff --git a/build-logic/src/main/kotlin/app/passwordstore/gradle/signing/AppSigning.kt b/build-logic/src/main/kotlin/app/passwordstore/gradle/signing/AppSigning.kt index f6a519c8..2a0825ed 100644 --- a/build-logic/src/main/kotlin/app/passwordstore/gradle/signing/AppSigning.kt +++ b/build-logic/src/main/kotlin/app/passwordstore/gradle/signing/AppSigning.kt @@ -5,7 +5,7 @@ package app.passwordstore.gradle.signing -import com.android.build.gradle.internal.dsl.BaseAppModuleExtension +import com.android.build.api.dsl.ApplicationExtension import java.util.Properties import org.gradle.api.Project import org.gradle.kotlin.dsl.configure @@ -17,7 +17,7 @@ private const val KEYSTORE_CONFIG_PATH = "keystore.properties" internal fun Project.configureBuildSigning() { val keystoreConfigFile = rootProject.layout.projectDirectory.file(KEYSTORE_CONFIG_PATH) if (keystoreConfigFile.asFile.exists()) { - extensions.configure { + extensions.configure { val contents = providers.fileContents(keystoreConfigFile).asText val keystoreProperties = Properties() keystoreProperties.load(contents.get().byteInputStream()) @@ -30,7 +30,7 @@ internal fun Project.configureBuildSigning() { } } val signingConfig = signingConfigs.getByName("release") - buildTypes.all { setSigningConfig(signingConfig) } + buildTypes.all { this.signingConfig = signingConfig } } } } diff --git a/gradle.properties b/gradle.properties index dde45722..b407f189 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,16 +18,7 @@ kotlin.incremental=true # Kotlin code style kotlin.code.style=official -# Enable incremental annotation processor for KAPT -kapt.incremental.apt=true - -# Turn off AP discovery in compile path to enable compile avoidance -kapt.include.compile.classpath=false - -# New incremental compilation for Kotlin -kotlin.compiler.preciseCompilationResultsBackup=true kotlin.build.report.output=file -kotlin.compiler.suppressExperimentalICOptimizationsWarning=true # Make all Kotlin DSL warnings fail build org.gradle.kotlin.dsl.allWarningsAsErrors=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8cc0acf2..fb831d85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] -agp = "8.13.2" +agp = "9.5.0" androidxActivity = "1.9.2" bouncycastle = "1.86" coroutines = "1.9.0" flowbinding = "1.2.0" -hilt = "2.58" -kotlin = "2.1.21" +hilt = "2.60.1" +kotlin = "2.4.21" kotlinResult = "2.0.0" lifecycle = "2.8.6" moshi = "1.15.1" @@ -36,18 +36,18 @@ aps-sublimeFuzzy = "com.github.android-password-store:sublime-fuzzy:2.3.4" aps-zxingAndroidEmbedded = "com.github.android-password-store:zxing-android-embedded:4.2.1" build-agp = { module = "com.android.tools.build:gradle", version.ref = "agp" } build-diffutils = "io.github.java-diff-utils:java-diff-utils:4.12" -build-download = "de.undercouch:gradle-download-task:5.6.0" +build-download = "de.undercouch:gradle-download-task:5.7.0" build-javapoet = "com.squareup:javapoet:1.13.0" build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -build-mavenpublish = "com.vanniktech:gradle-maven-publish-plugin:0.29.0" -build-metalava = "me.tylerbwong.gradle.metalava:plugin:0.3.5" +build-mavenpublish = "com.vanniktech:gradle-maven-publish-plugin:0.37.0" +build-metalava = "me.tylerbwong.gradle.metalava:plugin:0.5.1" build-moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } build-moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } build-okhttp = "com.squareup.okhttp3:okhttp:5.0.0-alpha.14" build-semver = "com.github.zafarkhaja:java-semver:0.10.2" -build-sentry = "io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin:4.11.0" -build-spotless = "com.diffplug.spotless:spotless-plugin-gradle:7.0.0.BETA2" -build-vcu = "nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin:0.8.4" +build-sentry = "io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin:6.25.0" +build-spotless = "com.diffplug.spotless:spotless-plugin-gradle:8.10.4" +build-vcu = "nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin:1.1.2" compose-bom = "androidx.compose:compose-bom:2024.09.03" compose-foundation-core = { module = "androidx.compose.foundation:foundation" } compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" } @@ -86,7 +86,7 @@ thirdparty-leakcanary-plumber = "com.squareup.leakcanary:plumber-android-startup thirdparty-logcat = "com.squareup.logcat:logcat:0.1" thirdparty-modernAndroidPrefs = "de.maxr1998:modernandroidpreferences:2.4.0-beta2" thirdparty-nonfree-googlePlayAuthApiPhone = "com.google.android.gms:play-services-auth-api-phone:18.1.0" -thirdparty-nonfree-sentry = "io.sentry:sentry-android:7.15.0" +thirdparty-nonfree-sentry = "io.sentry:sentry-android:8.62.0" thirdparty-pgpainless = "org.pgpainless:pgpainless-core:1.7.8" thirdparty-slack-lints = "com.slack.lint:slack-lint-checks:0.8.0" thirdparty-slf4j-api = { module = "org.slf4j:slf4j-api", version = { strictly = "[1.7, 1.8[", prefer = "1.7.36" } } @@ -106,4 +106,5 @@ testDependencies = [ [plugins] hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +ksp = "com.google.devtools.ksp:2.3.13" kotlin-composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 02f06ab2..9fa7f288 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f900c206..5907d39c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,8 +1,10 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7988ed071b2a07900e2ec715fca15c6ab72bce6db433af301fa7fa7e9407bd24 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.6-bin.zip +distributionSha256Sum=21bd5da2de49b642904077f38c0efdc7778e92b3ed0889392dca61b97e389607 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 23d15a93..249efbb0 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee8..03754c4c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,12 +19,39 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -45,13 +72,14 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. -goto fail +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -59,36 +87,26 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. -goto fail +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :execute @rem Setup the command line -set CLASSPATH= -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 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! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem This label must not be changed. We rely on old scripts being able to jump to this point. +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/passgen/diceware/build.gradle.kts b/passgen/diceware/build.gradle.kts index 413d9a0d..5caaa366 100644 --- a/passgen/diceware/build.gradle.kts +++ b/passgen/diceware/build.gradle.kts @@ -10,7 +10,9 @@ plugins { android { namespace = "app.passwordstore.passgen.diceware" androidResources.enable = true - sourceSets { getByName("test") { resources.srcDir("src/main/res/raw") } } + sourceSets { getByName("test") { resources.directories.add("src/main/res/raw") } } + // AGP 9 applies packaging excludes to unit test resources too; keep the word list. + packaging.resources.excludes.remove("**/*.txt") } dependencies { diff --git a/settings.gradle.kts b/settings.gradle.kts index 04160f17..a27a0cbd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,7 +32,7 @@ pluginManagement { } } -plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } +plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) -- cgit v1.2.3