From e71d2ce7216186bc73af124e04e4ae67b36c5728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Thu, 2 Jan 2025 14:29:45 +0000 Subject: [PATCH] AND-116: SDK size reporting (APK comparison) (#5539) * Compare APKs to calculate SDK sizes * Use release to build the comparison table * Trigger updates on merging to develop * changes after rebasing from develop which contains version catalog and kts refactoring * add a single android application project for metrics * rename build flavors to simplify logic in the CI script * check sizes using the single metrics android project * update sizes using the single metrics android project * Update SDK size metrics * Update SDK size badges * Remove test setup * Add a README note for the SDK sizes * Use GITHUB_TOKEN instead of the personal token * test * Fix * Push changes * Remove multiple metrics modules * Remove test setup --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/sdk-size-checks.yml | 121 ++++++++++++++++++ .github/workflows/sdk-size-updates.yml | 95 ++++++++++++++ README.md | 11 ++ build.gradle.kts | 1 + metrics/size.json | 14 ++ .../build.gradle.kts | 78 +++++++++++ .../src/main/AndroidManifest.xml | 33 +++++ .../chat/android/metrics/MainActivity.kt | 21 +++ settings.gradle.kts | 1 + 9 files changed, 375 insertions(+) create mode 100644 .github/workflows/sdk-size-checks.yml create mode 100644 .github/workflows/sdk-size-updates.yml create mode 100644 metrics/size.json create mode 100644 metrics/stream-chat-android-metrics/build.gradle.kts create mode 100644 metrics/stream-chat-android-metrics/src/main/AndroidManifest.xml create mode 100644 metrics/stream-chat-android-metrics/src/main/kotlin/io/getstream/chat/android/metrics/MainActivity.kt diff --git a/.github/workflows/sdk-size-checks.yml b/.github/workflows/sdk-size-checks.yml new file mode 100644 index 00000000000..85c67f01e59 --- /dev/null +++ b/.github/workflows/sdk-size-checks.yml @@ -0,0 +1,121 @@ +name: SDK size checks + +on: + pull_request: + +env: + METRICS_PROJECT: "stream-chat-android-metrics" + MODULES: "stream-chat-android-client stream-chat-android-offline stream-chat-android-ui-components stream-chat-android-compose" + METRICS_FILE: "metrics/size.json" + MAX_TOLERANCE: 500 + FINE_TOLERANCE: 250 + +jobs: + compare-sdk-sizes: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + - uses: ./.github/actions/setup-java + - uses: ./.github/actions/gradle-cache + with: + key-prefix: gradle-build + + - name: Assemble release for metrics + run: ./gradlew :metrics:$METRICS_PROJECT:assembleRelease + + - name: Get current SDK sizes + run: | + # Reads current SDK sizes from the metrics file + # and define to a variable using a compact JSON format + # so it can be exported for the next job step + CURRENT_SDK_SIZES=$(jq -c .release $METRICS_FILE) + echo "CURRENT_SDK_SIZES=$CURRENT_SDK_SIZES" >> $GITHUB_ENV + + - name: Calculate PR branch SDK sizes + run: | + echo '{}' > pr_sdk_sizes.json + + # Calculate sizes from the .apk files and save them into a temporary JSON file + # so it can be exported for the next job step + for module in $MODULES; do + baselineFile="metrics/$METRICS_PROJECT/build/outputs/apk/$module-baseline/release/$METRICS_PROJECT-$module-baseline-release.apk" + streamFile="metrics/$METRICS_PROJECT/build/outputs/apk/$module-stream/release/$METRICS_PROJECT-$module-stream-release.apk" + + baselineSize=$(du -k "$baselineFile" | awk '{print $1}') + streamSize=$(du -k "$streamFile" | awk '{print $1}') + size=$((streamSize - baselineSize)) + + jq -c --arg sdk "$module" --arg size "$size" '. + {($sdk): ($size | tonumber)}' pr_sdk_sizes.json > temp.json && mv temp.json pr_sdk_sizes.json + done + + echo "PR_SDK_SIZES=$(cat pr_sdk_sizes.json)" >> $GITHUB_ENV + + - name: Post comment on PR + uses: actions/github-script@v6 + with: + script: | + const maxTolerance = process.env.MAX_TOLERANCE + const fineTolerance = process.env.FINE_TOLERANCE + const currentSdkSizes = process.env.CURRENT_SDK_SIZES ? JSON.parse(process.env.CURRENT_SDK_SIZES) : {}; + const prSdkSizes = JSON.parse(process.env.PR_SDK_SIZES); + const commentHeader = '## SDK Size Comparison 📏'; + + // Prepare the comparison table + + let commentBody = ` + ${commentHeader} + + | SDK | Before | After | Difference | Status | + |-|-|-|-|-| + `; + + Object.keys(prSdkSizes).forEach(sdk => { + const currentSize = currentSdkSizes[sdk] || 0; + const prSize = prSdkSizes[sdk]; + const diff = prSize - currentSize; + const currentSizeInMb = (currentSize / 1024).toFixed(2); + const prSizeInMb = (prSize / 1024).toFixed(2); + const diffInMb = (diff / 1024).toFixed(2); + + let status = "🟢"; + if (diff < 0) { + status = "🚀"; + } else if (diff >= maxTolerance) { + status = "🔴"; + } else if (diff >= fineTolerance) { + status = "🟡"; + } + + commentBody += `| ${sdk} | ${currentSizeInMb} MB | ${prSizeInMb} MB | ${diffInMb} MB | ${status} |\n`; + }); + + // Post or update the PR comment + + // Find existing comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + + const comment = comments.find(c => c.body.includes(commentHeader)); + + if (comment) { + // Update the existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id, + body: commentBody, + }); + } else { + // Create a new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: commentBody, + }); + } diff --git a/.github/workflows/sdk-size-updates.yml b/.github/workflows/sdk-size-updates.yml new file mode 100644 index 00000000000..aa6914d1dd2 --- /dev/null +++ b/.github/workflows/sdk-size-updates.yml @@ -0,0 +1,95 @@ +name: SDK size updates + +on: + push: + branches: + - develop + + workflow_dispatch: + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +env: + METRICS_PROJECT: "stream-chat-android-metrics" + MODULES: "stream-chat-android-client stream-chat-android-offline stream-chat-android-ui-components stream-chat-android-compose" + VARIANTS: "debug release" + METRICS_FILE: "metrics/size.json" + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + +jobs: + update-sdk-sizes: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: ./.github/actions/setup-java + - uses: ./.github/actions/gradle-cache + with: + key-prefix: gradle-build + + - name: Assemble release and debug for metrics + run: ./gradlew :metrics:$METRICS_PROJECT:assemble + + - name: Update size metrics + run: | + # Create temporary JSON file + echo '{}' > metrics.json + + # Calculate sizes + for module in $MODULES; do + for variant in $VARIANTS; do + baselineFile="metrics/$METRICS_PROJECT/build/outputs/apk/$module-baseline/$variant/$METRICS_PROJECT-$module-baseline-$variant.apk" + streamFile="metrics/$METRICS_PROJECT/build/outputs/apk/$module-stream/$variant/$METRICS_PROJECT-$module-stream-$variant.apk" + + # Ensure files exist + if [[ -f "$baselineFile" && -f "$streamFile" ]]; then + baselineSize=$(du -k "$baselineFile" | awk '{print $1}') + streamSize=$(du -k "$streamFile" | awk '{print $1}') + size=$((streamSize - baselineSize)) + else + echo "Warning: $baselineFile or $streamFile not found. Setting size to 0." + size=0 + fi + + # Update JSON + jq --arg module "$module" --arg variant "$variant" --argjson size "$size" \ + ".\"$variant\".\"$module\" = $size" metrics.json > temp.json && mv temp.json metrics.json + done + done + + # Validate Generated JSON + jq . metrics.json + + # Move temporary JSON file to the final file + mv metrics.json $METRICS_FILE + + - name: Update size badges + run: | + for module in $MODULES; do + size=$(jq --arg module "$module" ".release.\"$module\"" $METRICS_FILE) + sizeInMb=$(echo "scale=2; $size / 1024" | bc) + badgeUrl="https://img.shields.io/badge/${module//-/--}-$sizeInMb%20MB-lightgreen" + sed -i "s|!\[$module\](.*)|![$module](${badgeUrl})|" README.md + done + + - name: Commit changes + run: | + git fetch origin $BRANCH_NAME + git checkout $BRANCH_NAME + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add $METRICS_FILE README.md + git commit -m "Update SDK sizes" || echo "No changes to commit" + + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.STREAM_PUBLIC_BOT_TOKEN }} + branch: ${{ env.BRANCH_NAME }} diff --git a/README.md b/README.md index 88ee120d48e..5b39546c876 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,17 @@

+
+ +![stream-chat-android-client](https://img.shields.io/badge/stream--chat--android--client-3.04%20MB-lightgreen) +![stream-chat-android-offline](https://img.shields.io/badge/stream--chat--android--offline-3.24%20MB-lightgreen) +![stream-chat-android-ui-components](https://img.shields.io/badge/stream--chat--android--ui--components-7.90%20MB-lightgreen) +![stream-chat-android-compose](https://img.shields.io/badge/stream--chat--android--compose-9.03%20MB-lightgreen) + +
+ +> **Note:** The SDK sizes reflect the maximum possible addition if none of their internal dependencies are already in your app. In most cases, the actual impact will be smaller. + This is the official Android SDK for [Stream Chat](https://getstream.io/chat/sdk/android/), a service for building chat and messaging applications. This library includes both a low-level chat SDK and a set of reusable UI components. Most users start with the UI components, and fall back to the lower level API when they want to customize things. We're proud to say that we're the first Android Chat SDK that supports Jetpack Compose! We [released](https://github.com/GetStream/stream-chat-android/releases/tag/4.15.0) our Compose UI Components one day after the official Jetpack Compose 1.0 release and our team members have been working hard on it since then. diff --git a/build.gradle.kts b/build.gradle.kts index a2af8c4bc4d..6fe20b66dd8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -95,6 +95,7 @@ apiValidation { "stream-chat-android-test", "stream-chat-android-compose-sample", "stream-chat-android-ui-guides", + "stream-chat-android-metrics", ) nonPublicMarkers += listOf( diff --git a/metrics/size.json b/metrics/size.json new file mode 100644 index 00000000000..5ea05dc55a9 --- /dev/null +++ b/metrics/size.json @@ -0,0 +1,14 @@ +{ + "debug": { + "stream-chat-android-client": 15036, + "stream-chat-android-offline": 15336, + "stream-chat-android-ui-components": 21028, + "stream-chat-android-compose": 22716 + }, + "release": { + "stream-chat-android-client": 3116, + "stream-chat-android-offline": 3324, + "stream-chat-android-ui-components": 8096, + "stream-chat-android-compose": 9256 + } +} diff --git a/metrics/stream-chat-android-metrics/build.gradle.kts b/metrics/stream-chat-android-metrics/build.gradle.kts new file mode 100644 index 00000000000..e7661abedeb --- /dev/null +++ b/metrics/stream-chat-android-metrics/build.gradle.kts @@ -0,0 +1,78 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +apply(from = "$rootDir/scripts/android.gradle") + +android { + namespace = "io.getstream.chat.android.metrics" + buildTypes { + release { + signingConfig = signingConfigs.findByName("debug") + } + } + + flavorDimensions += "sdk" + + productFlavors { + create("stream-chat-android-client-baseline") { + dimension = "sdk" + } + create("stream-chat-android-client-stream") { + dimension = "sdk" + } + create("stream-chat-android-offline-baseline") { + dimension = "sdk" + } + create("stream-chat-android-offline-stream") { + dimension = "sdk" + } + create("stream-chat-android-compose-baseline") { + dimension = "sdk" + } + create("stream-chat-android-compose-stream") { + dimension = "sdk" + } + create("stream-chat-android-ui-components-baseline") { + dimension = "sdk" + } + create("stream-chat-android-ui-components-stream") { + dimension = "sdk" + } + } +} + +afterEvaluate { + android.productFlavors.forEach { flavor -> + val flavorName = flavor.name + // For compose flavors, we apply the compose plugin, + // set up build features and add common compose dependencies. + if (flavorName.contains("compose")) { + val composePlugin = libs.plugins.kotlin.compose.get() + plugins.apply(composePlugin.pluginId) + android.buildFeatures.compose = true + val configurationName = "${flavorName}Implementation" + dependencies.add(configurationName, libs.androidx.compose.ui) + dependencies.add(configurationName, libs.androidx.compose.ui.tooling) + dependencies.add(configurationName, libs.androidx.compose.foundation) + dependencies.add(configurationName, libs.androidx.activity.compose) + dependencies.add(configurationName, libs.androidx.lifecycle.runtime.compose) + dependencies.add(configurationName, libs.androidx.lifecycle.viewmodel.compose) + } + } +} + +dependencies { + detektPlugins(libs.detekt.formatting) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.androidx.appcompat) + + "stream-chat-android-client-streamImplementation"(project(":stream-chat-android-client")) + + "stream-chat-android-offline-streamImplementation"(project(":stream-chat-android-offline")) + + "stream-chat-android-ui-components-streamImplementation"(project(":stream-chat-android-ui-components")) + + "stream-chat-android-compose-streamImplementation"(project(":stream-chat-android-compose")) +} diff --git a/metrics/stream-chat-android-metrics/src/main/AndroidManifest.xml b/metrics/stream-chat-android-metrics/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..0b34f9b8797 --- /dev/null +++ b/metrics/stream-chat-android-metrics/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/metrics/stream-chat-android-metrics/src/main/kotlin/io/getstream/chat/android/metrics/MainActivity.kt b/metrics/stream-chat-android-metrics/src/main/kotlin/io/getstream/chat/android/metrics/MainActivity.kt new file mode 100644 index 00000000000..a5ecb2c9143 --- /dev/null +++ b/metrics/stream-chat-android-metrics/src/main/kotlin/io/getstream/chat/android/metrics/MainActivity.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.metrics + +import androidx.activity.ComponentActivity + +class MainActivity : ComponentActivity() diff --git a/settings.gradle.kts b/settings.gradle.kts index c31a5dd3d0a..ec1e49f2816 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -67,6 +67,7 @@ include ( ":stream-chat-android-ui-uitests", ":stream-chat-android-benchmark", ":stream-chat-android-ai-assistant", + ":metrics:stream-chat-android-metrics", ) dependencyResolutionManagement {