diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ef7fa81acfb..a60938ffa90 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -257,4 +257,129 @@ platform :android do ) releaseNotes end + + # Authenticator + desc "Runs tests" + lane :checkAuthenticator do + gradle(tasks: ["authenticator:testDebug", "authenticator:lintDebug", "authenticator:detekt","authenticator:koverXmlReportDebug"]) + end + + desc "Apply build version information" + fastlane_require "time" + lane :setAuthenticatorBuildVersionInfo do |options| + + # Read-in app build config file. + buildConfigPath = "../authenticator/build.gradle.kts" + buildConfigFile = File.open(buildConfigPath) + buildConfigText = buildConfigFile.read + buildConfigFile.close + + currentVersionCode = buildConfigText.match(/versionCode = (\d+)/).captures[0] + currentVersionName = buildConfigText.match(/versionName = "(.+)"/).captures[0] + + if options[:versionName].nil? or options[:versionName].to_s.empty? + puts "Fetching latest tags from origin..." + `git fetch --prune --no-recurse-submodules --filter=tree:0 --depth=1 --tags origin` + puts "Getting latest version name from previous git tag..." + latestTag = `git describe --tags $(git rev-list --tags --max-count=1)`.chomp() + puts "Using tag #{latestTag} to calculate version name..." + latestTag.slice!(0) + puts "Current version name resolved to #{latestTag}." + + versionParts = latestTag.split(".") + currentMajor = versionParts[0] + currentMinor = versionParts[1] + currentRevision = versionParts[2] + + currentDate = Time.new + major = currentDate.year.to_s + minor = currentDate.strftime "%-m" + + revision = 0 + if currentMajor == major and currentMinor == minor + revision = currentRevision.to_i + 1 + end + nextVersionName = "#{major}.#{minor}.#{revision}" + else + nextVersionName = options[:versionName].to_s + end + + # Replace version information. + puts "Setting version code to #{options[:versionCode]}." + buildConfigText.gsub!("versionCode = #{currentVersionCode}", "versionCode = #{options[:versionCode]}") + puts "Setting version name to #{nextVersionName}." + buildConfigText.gsub!("versionName = \"#{currentVersionName}\"", "versionName = \"#{nextVersionName}\"") + + # Save changes + File.open(buildConfigPath, "w") { |buildConfigFile| buildConfigFile << buildConfigText } + end + + desc "Assemble debug variants" + lane :buildAuthenticatorDebug do + gradle( + task: "authenticator:assemble", + build_type: "Debug", + print_command: false, + ) + end + + desc "Assemble and sign release APK" + lane :buildAuthenticatorRelease do |options| + gradle( + task: "authenticator:assemble", + build_type: "Release", + properties: { + "android.injected.signing.store.file" => options[:storeFile], + "android.injected.signing.store.password" => options[:storePassword], + "android.injected.signing.key.alias" => options[:keyAlias], + "android.injected.signing.key.password" => options[:keyPassword] + }, + print_command: false, + ) + end + + desc "Bundle and sign release AAB" + lane :bundleAuthenticatorRelease do |options| + gradle( + task: "authenticator:bundle", + build_type: "Release", + properties: { + "android.injected.signing.store.file" => options[:storeFile], + "android.injected.signing.store.password" => options[:storePassword], + "android.injected.signing.key.alias" => options[:keyAlias], + "android.injected.signing.key.password" => options[:keyPassword] + }, + print_command: false, + ) + end + + desc "Publish release AAB to Firebase" + lane :distributeAuthenticatorReleaseBundleToFirebase do |options| + release_notes = changelog_from_git_commits( + commits_count: 1, + pretty: "- %s" + ) + + puts "Release notes #{release_notes}" + + firebase_app_distribution( + app: "1:867301491091:android:50b626dba42a361651e866", + android_artifact_type: "AAB", + android_artifact_path: "authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab", + service_credentials_file: options[:serviceCredentialsFile], + groups: "internal-prod-group, livefront", + release_notes: release_notes, + ) + end + + desc "Publish release to Google Play Store" + lane :publishAuthenticatorReleaseToGooglePlayStore do |options| + upload_to_play_store( + package_name: "com.bitwarden.authenticator", + json_key: options[:serviceCredentialsFile], + track: "internal", + aab: "authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab", + mapping: "authenticator/build/outputs/mapping/release/mapping.txt", + ) + end end diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ad031875268..ba3f0b9a9a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ compileSdk = "35" targetSdk = "35" minSdk = "29" +minSdkBwa = "28" # Dependency Versions androidGradlePlugin = "8.8.1" @@ -23,6 +24,8 @@ androidxNavigation = "2.8.0" androidxRoom = "2.6.1" androidXSecurityCrypto = "1.1.0-alpha06" androidxSplash = "1.1.0-rc01" +androidxTest = "1.6.2" +androidxTestRules = "1.6.1" androidXAppCompat = "1.7.0" androdixAutofill = "1.1.0" androidxWork = "2.10.0" @@ -30,7 +33,12 @@ bitwardenSdk = "1.0.0-20250213.181812-113" crashlytics = "3.0.3" detekt = "1.23.7" firebaseBom = "33.9.0" +espresso = "3.6.1" +fastlaneScreengrab = "2.1.1" glide = "1.0.0-beta01" +googleGuava = "33.4.0-jre" +googleProtoBufJava = "4.29.3" +googleProtoBufPlugin = "0.9.4" googleServices = "4.4.2" googleReview = "2.0.2" hilt = "2.55" @@ -52,6 +60,7 @@ timber = "5.0.1" turbine = "1.2.0" zxcvbn4j = "1.9.0" zxing = "3.5.3" +testng = "7.10.2" [libraries] # Format: - @@ -86,18 +95,26 @@ androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom" } androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "androidXSecurityCrypto" } androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" } +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTest" } +androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidxTestRules" } +androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" } bitwarden-sdk = { module = "com.bitwarden:sdk-android-temp", version.ref = "bitwardenSdk" } bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" } detekt-detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } detekt-detekt-rules = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" } +fastlane-screengrab = { module = "tools.fastlane:screengrab", version.ref = "fastlaneScreengrab" } google-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } google-firebase-cloud-messaging = { module = "com.google.firebase:firebase-messaging-ktx" } google-firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" } +google-guava = { module = "com.google.guava:guava", version.ref = "googleGuava" } google-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } google-hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } google-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } google-play-review = { module = "com.google.android.play:review", version.ref = "googleReview" } +google-protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "googleProtoBufJava" } +# Included so that Renovate tracks updates since protoc is not referenced directly in `dependency {}` blocks. +google-protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "googleProtoBufJava" } junit-junit5 = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } junit-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } @@ -105,6 +122,7 @@ kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } +mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } mockk-mockk = { module = "io.mockk:mockk", version.ref = "mockk" } nulab-zxcvbn4j = { module = "com.nulab-inc:zxcvbn", version.ref = "zxcvbn4j" } robolectric-robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } @@ -117,12 +135,14 @@ square-retrofit-kotlinx-serialization = { module = "com.squareup.retrofit2:conve square-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } zxing-zxing-core = { module = "com.google.zxing:core", version.ref = "zxing" } +testng = { group = "org.testng", name = "testng", version.ref = "testng" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +google-protobuf = { id = "com.google.protobuf", version.ref = "googleProtoBufPlugin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }