From 23def1b2e0048e9306962a7f65641c75e12950b3 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 12 Jun 2024 10:39:49 -0700 Subject: [PATCH] Fix part of #1719, part of #3709: Add build stats CI workflow (#4092) ## Explanation Fixes part of #1719 and #3709 This PR introduces a new script & CI workflow for computing build stats to compare both AABs and universal APKs between develop and the changes in a given PR, as part of fixing #1719 (though this PR doesn't cover everything outlined in that PR). This information is then detailed and uploaded as a CI build artifact, and summarized & posted as a comment in the PR. Some details included in the summary report: - APK file/download size differences - Method count differences - Feature/permission differences - New/removed resources & assets The script supports computing differences for multiple "profiles" at the same time, and the CI workflow has been set up to compute four: 1. dev 2. alpha 3. beta 4. GA This workflow will be optional since it's very expensive to run (it has to assemble 8 builds, 6 of which are Proguarded). It also doesn't really need to be run in order to approve a PR, though reviewers may insist on waiting for large or suspicious changes (such as PRs introducing new dependencies) to ensure the actual affected changes are as expected. In order to mitigate this expense, the CI workflow runs on a scheduled cron job off of develop across all open PRs and checks them in a group. It runs at most once per day (based on https://github.com/orgs/community/discussions/55768#discussioncomment-5941720) so multiple changes to a PR will be picked up with a single check in the next cron run. Currently, it will run even for a PR that hasn't changed since the last run (but this is something that can be improved in the future if it needs to be). It's being scheduled for 2:30am (02:30) UTC which seems to have a few specific benefits: - Per GitHub documentation, initiating the workflow outside the start of the hour should reduce likelihood of cancellation (since the start of the hour tends to use the most resources): https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule. - This corresponds to 7:30pm PT, 2:30am GMT, 8:00am IST, 5:30am EAT, and 12:30pm AEST (just as a basis for a different part of the world). It's actually a very nice time that shouldn't overlap with almost any development in main locations around the world, so it hopefully won't impact Oppia organization GitHub CI resources. The example output from these workflows can be observed in a few places: - Later in this PR (for back when the PR was configured to run the new workflow per PR change). - In #4261 which demonstrates the large math PRs and how they changed the builds back before those were merged. - https://github.com/BenHenning/oppia-android/actions/workflows/stats.yml and https://github.com/BenHenning/oppia-android/pulls (specifically: https://github.com/BenHenning/oppia-android/pull/14, https://github.com/BenHenning/oppia-android/pull/13, https://github.com/BenHenning/oppia-android/pull/12) which demonstrates the workflow running correctly from a scheduled cron (https://github.com/BenHenning/oppia-android/actions/runs/9232187176) and posting the updates to open PRs. Beyond that, implementing this utility involved several significant changes to various systems, including the build graph: - Three new utilities were added for the script: Aapt2Client, ApkAnalyzerClient, and BundleToolClient. Each of these correspond to Android CLI utilities, but each required special considerations: - Aapt2Client requires direct access to the Android SDK, but fortunately android_sdk_repository exposes this as a target so it's trivial to pass it in & call it. Some build information is needed, too (see next outer point). - ApkAnalyzerClient couldn't use the apkanalyzer CLI contained within the SDK since it's not exported by android_sdk_repository. Instead, we needed to depend on the CLI's internal implementation library (which I suspect is what Android Studio probably uses for its own APK Analyzer tool). This required some new implementation. - BundleToolClient fortunately can call right into the bundle tool library that we use when building AABs, but unfortunately that tool appears to not be designed to be called multiple times in the same process. Because Java doesn't support forking, we actually needed to fake a fork function by starting a new Java process using the current process's classpath in order to re-run bundle tool for each needed routine. Additionally, bundle tool required https://github.com/oppia/archive-patcher (which needed new BUILD files since it only supported Gradle building previously) and a non-Android version of Guava (see below for the changes this has caused). - A new build_vars.bzl was introduced to define the build SDK & build tools versions (this is done in a way where they can actually be passed to the new script's utilities since it needs to access aapt2). - rules_kotlin had a bug where resources wouldn't be pulled in properly for kt_jvm_library (see https://github.com/bazelbuild/rules_kotlin/issues/281), but this was mitigated in a previous PR by upgrading rules_kotlin past alpha 2. - The new functionality required the JRE-compatible version of Guava (over the Android-constrained library used in the codebase today), but this introduces a one-version issue. The solution ended up being isolating the JRE-compatible Guava library to its own library with a slightly hacky direct reference to it in BundleToolClient. Some of the other attempts at solving this resulted in some Maven reference cleanups in existing script documentation. This functionality will be improved in downstream PRs, but other attempts that were originally made to isolate this cleanly were: - Introduce multiple maven_install files and isolate dependencies into: production, tests, scripts. This has a number of nice benefits (more correct licenses and faster Maven dependency fetches for production), but it results in very tricky one-version violations for test targets that cross dependencies between production and tests. - Isolated maven_install just for scripts. This is closer to the solution we'll want long-term, but it was too much complexity to fully introduce in this PR so it's been reworked into a downstream PR that can focus on cleaning up third-party dependency management across the whole codebase. This PR is introducing a few new dependencies that, in turn, pull in a *bunch* of transitive dependencies. These are all due to the new ``apkanalyzer`` dependency. While it will affect licenses for this specific PR, once third-party dependencies for scripts are cleaned up in a downstream PR they will be moved out (since they are script-only dependencies). Separately, also note that the AAPT2 utility requires stdout to be processed continuously in order for the process to finish. This was one of the primary reasons CommandExecutorImpl was reworked in #4929. For testing: most of the changes in this PR have been extensively manually tested. However, the new utilities are lacking significant automated tests. Since this utility is a nice-to-have for the rest of the Bazel PR chain, it's being prioritized to be merged in spite of lacking code coverage. #4971 has been filed to track adding these missing tests in the long-term. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only N/A -- This only affects CI workflows & the build system. Technically, some dependency changes in the build system could have UI effects, but there should be no such changes in this PR. --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Co-authored-by: Sean Lip --- .github/workflows/stats.yml | 216 ++ WORKSPACE | 41 +- build_vars.bzl | 2 + oppia_android_application.bzl | 4 +- scripts/BUILD.bazel | 10 + .../android/scripts/apkstats/Aapt2Client.kt | 69 + .../scripts/apkstats/ApkAnalyzerClient.kt | 105 + .../android/scripts/apkstats/BUILD.bazel | 64 + .../scripts/apkstats/BundleToolClient.kt | 130 + .../scripts/apkstats/ComputeAabDifferences.kt | 798 ++++++ .../common/AndroidBuildSdkProperties.kt | 32 + .../oppia/android/scripts/common/BUILD.bazel | 22 + .../android/scripts/common/BazelClient.kt | 2 +- .../license/MavenDependenciesListCheck.kt | 6 +- .../license/MavenDependenciesRetriever.kt | 10 +- .../maven/GenerateMavenDependenciesList.kt | 4 +- .../scripts/maven/RetrieveLicenseTexts.kt | 2 +- .../android/scripts/maven/model/BUILD.bazel | 2 +- .../scripts/apkstats/Aapt2ClientTest.kt | 62 + .../scripts/apkstats/ApkAnalyzerClientTest.kt | 65 + .../android/scripts/apkstats/BUILD.bazel | 56 + .../scripts/apkstats/BundleToolClientTest.kt | 51 + .../apkstats/ComputeAabDifferencesTest.kt | 69 + .../common/AndroidBuildSdkPropertiesTest.kt | 31 + .../oppia/android/scripts/common/BUILD.bazel | 9 + .../GenerateMavenDependenciesListTest.kt | 2 +- third_party/BUILD.bazel | 34 +- third_party/maven_install.json | 2210 ++++++++++++++++- third_party/versions.bzl | 5 + 29 files changed, 4039 insertions(+), 74 deletions(-) create mode 100644 .github/workflows/stats.yml create mode 100644 build_vars.bzl create mode 100644 scripts/src/java/org/oppia/android/scripts/apkstats/Aapt2Client.kt create mode 100644 scripts/src/java/org/oppia/android/scripts/apkstats/ApkAnalyzerClient.kt create mode 100644 scripts/src/java/org/oppia/android/scripts/apkstats/BUILD.bazel create mode 100644 scripts/src/java/org/oppia/android/scripts/apkstats/BundleToolClient.kt create mode 100644 scripts/src/java/org/oppia/android/scripts/apkstats/ComputeAabDifferences.kt create mode 100644 scripts/src/java/org/oppia/android/scripts/common/AndroidBuildSdkProperties.kt create mode 100644 scripts/src/javatests/org/oppia/android/scripts/apkstats/Aapt2ClientTest.kt create mode 100644 scripts/src/javatests/org/oppia/android/scripts/apkstats/ApkAnalyzerClientTest.kt create mode 100644 scripts/src/javatests/org/oppia/android/scripts/apkstats/BUILD.bazel create mode 100644 scripts/src/javatests/org/oppia/android/scripts/apkstats/BundleToolClientTest.kt create mode 100644 scripts/src/javatests/org/oppia/android/scripts/apkstats/ComputeAabDifferencesTest.kt create mode 100644 scripts/src/javatests/org/oppia/android/scripts/common/AndroidBuildSdkPropertiesTest.kt diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml new file mode 100644 index 00000000000..985d8a2ac5d --- /dev/null +++ b/.github/workflows/stats.yml @@ -0,0 +1,216 @@ +# Contains jobs corresponding to stats, including build stats due to changes in a PR. + +name: Stats Checks & Reports + +on: + workflow_dispatch: + schedule: + - cron: "30 02 * * *" + +permissions: + pull-requests: write + +jobs: + find_open_pull_requests: + name: Find open PRs + runs-on: ubuntu-20.04 + outputs: + matrix: ${{ steps.compute-pull-request-matrix.outputs.matrix }} + env: + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + + - name: Compute PR matrix + id: compute-pull-request-matrix + # Remove spaces to ensure the matrix output is on one line. Reference: + # https://stackoverflow.com/a/3232433. + run: | + CURRENT_OPEN_PR_INFO="$(gh pr list --json number,baseRefName,headRefName,headRepository,headRepositoryOwner | tr -d '[:space:]')" + echo "matrix={\"prInfo\": $CURRENT_OPEN_PR_INFO}" >> "$GITHUB_OUTPUT" + + build_stats: + name: Build Stats + needs: find_open_pull_requests + runs-on: ubuntu-20.04 + # Reduce parallelization due to high build times, and allow individual PRs to fail. + strategy: + fail-fast: false + max-parallel: 5 + matrix: ${{ fromJson(needs.find_open_pull_requests.outputs.matrix) }} + env: + ENABLE_CACHING: false + CACHE_DIRECTORY: ~/.bazel_cache + steps: + - name: Compute PR head owner/repo reference + env: + PR_HEAD_REPO: ${{ matrix.prInfo.headRepository.name }} + PR_HEAD_REPO_OWNER: ${{ matrix.prInfo.headRepositoryOwner.login }} + run: | + echo "PR_HEAD=$PR_HEAD_REPO_OWNER/$PR_HEAD_REPO" >> "$GITHUB_ENV" + - name: Print PR information for this run + env: + PR_BASE_REF_NAME: ${{ matrix.prInfo.baseRefName }} + PR_HEAD_REF_NAME: ${{ matrix.prInfo.headRefName }} + PR_NUMBER: ${{ matrix.prInfo.number }} + run: | + echo "PR $PR_NUMBER is merging into $PR_BASE_REF_NAME from https://github.com/$PR_HEAD branch $PR_HEAD_REF_NAME." + + - name: Set up JDK 9 + uses: actions/setup-java@v1 + with: + java-version: 9 + + - name: Set up Bazel + uses: abhinavsingh/setup-bazel@v3 + with: + version: 4.0.0 + + # For reference on this & the later cache actions, see: + # https://github.com/actions/cache/issues/239#issuecomment-606950711 & + # https://github.com/actions/cache/issues/109#issuecomment-558771281. Note that these work + # with Bazel since Bazel can share the most recent cache from an unrelated build and still + # benefit from incremental build performance (assuming that actions/cache aggressively removes + # older caches due to the 5GB cache limit size & Bazel's large cache size). + - uses: actions/cache@v2 + id: cache + with: + path: ${{ env.CACHE_DIRECTORY }} + key: ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary- + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel- + + # This check is needed to ensure that Bazel's unbounded cache growth doesn't result in a + # situation where the cache never updates (e.g. due to exceeding GitHub's cache size limit) + # thereby only ever using the last successful cache version. This solution will result in a + # few slower CI actions around the time cache is detected to be too large, but it should + # incrementally improve thereafter. + - name: Ensure cache size + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + # See https://stackoverflow.com/a/27485157 for reference. + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + CACHE_SIZE_MB=$(du -smc $EXPANDED_BAZEL_CACHE_PATH | grep total | cut -f1) + echo "Total size of Bazel cache (rounded up to MBs): $CACHE_SIZE_MB" + # Use a 4.5GB threshold since actions/cache compresses the results, and Bazel caches seem + # to only increase by a few hundred megabytes across changes for unrelated branches. This + # is also a reasonable upper-bound (local tests as of 2021-03-31 suggest that a full build + # of the codebase (e.g. //...) from scratch only requires a ~2.1GB uncompressed/~900MB + # compressed cache). + if [[ "$CACHE_SIZE_MB" -gt 4500 ]]; then + echo "Cache exceeds cut-off; resetting it (will result in a slow build)" + rm -rf $EXPANDED_BAZEL_CACHE_PATH + fi + + - name: Configure Bazel to use a local cache + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + echo "Using $EXPANDED_BAZEL_CACHE_PATH as Bazel's cache path" + echo "build --disk_cache=$EXPANDED_BAZEL_CACHE_PATH" >> $HOME/.bazelrc + shell: bash + + # This checks out the actual true develop branch separately to ensure that the stats check is + # run from the latest develop rather than the base branch (which might be different for + # chained PRs). + - name: Check out develop repository + uses: actions/checkout@v4 + with: + path: develop + + - name: Set up build environment + uses: ./develop/.github/actions/set-up-android-bazel-build-environment + + - name: Check Bazel environment + run: | + cd develop + bazel info + + - name: Check out base repository and branch + env: + PR_BASE_REF_NAME: ${{ matrix.prInfo.baseRefName }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.PR_BASE_REF_NAME }} + path: base + + - name: Check out head repository and branch + env: + PR_HEAD_REF_NAME: ${{ matrix.prInfo.headRefName }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: ${{ env.PR_HEAD }} + ref: ${{ env.PR_HEAD_REF_NAME }} + path: head + + # Note that Bazel is shutdown between builds since multiple Bazel servers will otherwise end + # up being active (due to multiple repositories being used) and this can quickly overwhelm CI + # worker resources. + - name: Build Oppia dev, alpha, beta, and GA (feature branch) + run: | + cd head + git log -n 1 + bazel build -- //:oppia_dev //:oppia_alpha //:oppia_beta //:oppia_ga + cp bazel-bin/oppia_dev.aab ../develop/oppia_dev_with_changes.aab + cp bazel-bin/oppia_alpha.aab ../develop/oppia_alpha_with_changes.aab + cp bazel-bin/oppia_beta.aab ../develop/oppia_beta_with_changes.aab + cp bazel-bin/oppia_ga.aab ../develop/oppia_ga_with_changes.aab + bazel shutdown + + - name: Build Oppia dev, alpha, beta, and GA (base branch) + run: | + cd base + git log -n 1 + bazel build -- //:oppia_dev //:oppia_alpha //:oppia_beta //:oppia_ga + cp bazel-bin/oppia_dev.aab ../develop/oppia_dev_without_changes.aab + cp bazel-bin/oppia_alpha.aab ../develop/oppia_alpha_without_changes.aab + cp bazel-bin/oppia_beta.aab ../develop/oppia_beta_without_changes.aab + cp bazel-bin/oppia_ga.aab ../develop/oppia_ga_without_changes.aab + bazel shutdown + + - name: Run stats analysis tool (develop branch) + run: | + cd develop + git log -n 1 + bazel run //scripts:compute_aab_differences -- \ + $(pwd)/brief_build_summary.log $(pwd)/full_build_summary.log \ + dev $(pwd)/oppia_dev_without_changes.aab $(pwd)/oppia_dev_with_changes.aab \ + alpha $(pwd)/oppia_alpha_without_changes.aab $(pwd)/oppia_alpha_with_changes.aab \ + beta $(pwd)/oppia_beta_without_changes.aab $(pwd)/oppia_beta_with_changes.aab \ + ga $(pwd)/oppia_ga_without_changes.aab $(pwd)/oppia_ga_with_changes.aab + + # Reference: https://github.com/peter-evans/create-or-update-comment#setting-the-comment-body-from-a-file. + # Also, for multi-line env values, see: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings. + - name: Extract reports for uploading & commenting + env: + PR_NUMBER: ${{ matrix.prInfo.number }} + id: compute-comment-body + run: | + { + echo 'comment_body<> "$GITHUB_OUTPUT" + FULL_BUILD_SUMMARY_FILE_NAME="full_build_summary_pr_$PR_NUMBER.log" + FULL_BUILD_SUMMARY_FILE_PATH="$GITHUB_WORKSPACE/develop/$FULL_BUILD_SUMMARY_FILE_NAME" + echo "FULL_BUILD_SUMMARY_FILE_NAME=$FULL_BUILD_SUMMARY_FILE_NAME" >> "$GITHUB_ENV" + echo "FULL_BUILD_SUMMARY_FILE_PATH=$FULL_BUILD_SUMMARY_FILE_PATH" >> "$GITHUB_ENV" + cp "$GITHUB_WORKSPACE/develop/full_build_summary.log" "$FULL_BUILD_SUMMARY_FILE_PATH" + + - name: Add build stats summary comment + env: + PR_NUMBER: ${{ matrix.prInfo.number }} + uses: peter-evans/create-or-update-comment@v1 + with: + issue-number: ${{ env.PR_NUMBER }} + body: ${{ steps.compute-comment-body.outputs.comment_body }} + + - uses: actions/upload-artifact@v2 + with: + name: ${{ env.FULL_BUILD_SUMMARY_FILE_NAME }} + path: ${{ env.FULL_BUILD_SUMMARY_FILE_PATH }} diff --git a/WORKSPACE b/WORKSPACE index dc4b29ece77..1232f4971ea 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -4,6 +4,7 @@ This file lists and imports all external dependencies needed to build Oppia Andr load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") +load("//:build_vars.bzl", "BUILD_SDK_VERSION", "BUILD_TOOLS_VERSION") load("//third_party:versions.bzl", "HTTP_DEPENDENCY_VERSIONS", "MAVEN_REPOSITORIES", "get_maven_dependencies") # Android SDK configuration. For more details, see: @@ -11,8 +12,8 @@ load("//third_party:versions.bzl", "HTTP_DEPENDENCY_VERSIONS", "MAVEN_REPOSITORI # TODO(#1542): Sync Android SDK version with the manifest. android_sdk_repository( name = "androidsdk", - api_level = 33, - build_tools_version = "29.0.2", + api_level = BUILD_SDK_VERSION, + build_tools_version = BUILD_TOOLS_VERSION, ) # Oppia's backend proto API definitions. @@ -160,6 +161,13 @@ git_repository( shallow_since = "1679426649 -0700", ) +git_repository( + name = "archive_patcher", + commit = "d1c18b0035d5f669ddaefadade49cae0748f9df2", + remote = "https://github.com/oppia/archive-patcher", + shallow_since = "1642022460 -0800", +) + bind( name = "databinding_annotation_processor", actual = "//tools/android:compiler_annotation_processor", @@ -214,17 +222,24 @@ load("@maven//:defs.bzl", "pinned_maven_install") pinned_maven_install() -http_jar( - name = "guava_android", - sha256 = HTTP_DEPENDENCY_VERSIONS["guava_android"]["sha"], - urls = [ - "{0}/com/google/guava/guava/{1}-android/guava-{1}-android.jar".format( - url_base, - HTTP_DEPENDENCY_VERSIONS["guava_android"]["version"], - ) - for url_base in DAGGER_REPOSITORIES + MAVEN_REPOSITORIES - ], -) +[ + http_jar( + name = "guava_%s" % guava_type, + sha256 = HTTP_DEPENDENCY_VERSIONS["guava_%s" % guava_type]["sha"], + urls = [ + "{0}/com/google/guava/guava/{1}-{2}/guava-{1}-{2}.jar".format( + url_base, + HTTP_DEPENDENCY_VERSIONS["guava_%s" % guava_type]["version"], + guava_type, + ) + for url_base in DAGGER_REPOSITORIES + MAVEN_REPOSITORIES + ], + ) + for guava_type in [ + "android", + "jre", + ] +] http_jar( name = "kotlinx-coroutines-core-jvm", diff --git a/build_vars.bzl b/build_vars.bzl new file mode 100644 index 00000000000..99b12eef5ae --- /dev/null +++ b/build_vars.bzl @@ -0,0 +1,2 @@ +BUILD_SDK_VERSION = 33 +BUILD_TOOLS_VERSION = "29.0.2" diff --git a/oppia_android_application.bzl b/oppia_android_application.bzl index e941becc4a4..d9951cefa18 100644 --- a/oppia_android_application.bzl +++ b/oppia_android_application.bzl @@ -280,7 +280,7 @@ _bundle_module_zip_into_deployable_aab = rule( "_bundletool_tool": attr.label( executable = True, cfg = "host", - default = "//third_party:android_bundletool", + default = "//third_party:android_bundletool_binary", ), }, implementation = _bundle_module_zip_into_deployable_aab_impl, @@ -316,7 +316,7 @@ _generate_apks_and_install = rule( "_bundletool_tool": attr.label( executable = True, cfg = "host", - default = "//third_party:android_bundletool", + default = "//third_party:android_bundletool_binary", ), }, executable = True, diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 2199e663d4e..689cf6e53d2 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -42,6 +42,16 @@ package_group( packages = ["//scripts/src/java/..."], ) +kt_jvm_binary( + name = "compute_aab_differences", + testonly = True, + data = ["@androidsdk//:aapt2_binary"], + main_class = "org.oppia.android.scripts.apkstats.ComputeAabDifferencesKt", + runtime_deps = [ + "//scripts/src/java/org/oppia/android/scripts/apkstats:compute_aab_differences_lib", + ], +) + kt_jvm_binary( name = "compute_affected_tests", testonly = True, diff --git a/scripts/src/java/org/oppia/android/scripts/apkstats/Aapt2Client.kt b/scripts/src/java/org/oppia/android/scripts/apkstats/Aapt2Client.kt new file mode 100644 index 00000000000..6e00ea41d79 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/apkstats/Aapt2Client.kt @@ -0,0 +1,69 @@ +package org.oppia.android.scripts.apkstats + +import org.oppia.android.scripts.common.CommandExecutor +import org.oppia.android.scripts.common.CommandExecutorImpl +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import java.io.File + +/** + * General utility for interfacing with AAPT2 in the local system at the specified working directory + * path and contained within the specified Android SDK (per the given path). + * + * Note that in order for binary dependencies to utilize this client, they must add a 'data' + * dependency on the AAPT2 binary included as part of the Android SDK, e.g.: + * + * ```bazel + * data = ["@androidsdk//:aapt2_binary"] + * ``` + * + * @property workingDirectoryPath the path to the working directory in which instances of AAPT2 + * should be executed + * @property buildToolsVersion the version of Android build tools installed & that should be used. + * This value should be coordinated with the build system used by the APKs accessed by this + * utility. + * @param scriptBgDispatcher the [ScriptBackgroundCoroutineDispatcher] to be used for running the + * AAPT2 command + * @property commandExecutor the [CommandExecutor] to use when accessing AAPT2 + */ +class Aapt2Client( + private val workingDirectoryPath: String, + private val buildToolsVersion: String, + scriptBgDispatcher: ScriptBackgroundCoroutineDispatcher, + private val commandExecutor: CommandExecutor = CommandExecutorImpl(scriptBgDispatcher) +) { + private val workingDirectory by lazy { File(workingDirectoryPath) } + // Note that this pathing will not work by default on Windows (since executables end with '.exe'). + private val aapt2Path by lazy { + File("external/androidsdk", "build-tools/$buildToolsVersion/aapt2").absolutePath + } + + // CLI reference: https://developer.android.com/studio/command-line/apkanalyzer. + + /** Returns the permissions dump as reported by AAPT2 for the specified APK. */ + fun dumpPermissions(inputApkPath: String): List { + return executeApkAnalyzerCommand("dump", "permissions", inputApkPath) + } + + /** Returns the resources dump as reported by AAPT2 for the specified APK. */ + fun dumpResources(inputApkPath: String): List { + return executeApkAnalyzerCommand("dump", "resources", inputApkPath) + } + + /** + * Returns badging information, that is, high-level details like supported locales and densities, + * from the specified APK's manifest. + */ + fun dumpBadging(inputApkPath: String): List { + return executeApkAnalyzerCommand("dump", "badging", inputApkPath) + } + + private fun executeApkAnalyzerCommand(vararg arguments: String): List { + val result = commandExecutor.executeCommand(workingDirectory, aapt2Path, *arguments) + check(result.exitCode == 0) { + "Expected zero exit code (not ${result.exitCode}) for command: ${result.command}." + + "\nStandard output:\n${result.output.joinToString("\n")}" + + "\nError output:\n${result.errorOutput.joinToString("\n")}" + } + return result.output + } +} diff --git a/scripts/src/java/org/oppia/android/scripts/apkstats/ApkAnalyzerClient.kt b/scripts/src/java/org/oppia/android/scripts/apkstats/ApkAnalyzerClient.kt new file mode 100644 index 00000000000..e24a8620318 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/apkstats/ApkAnalyzerClient.kt @@ -0,0 +1,105 @@ +package org.oppia.android.scripts.apkstats + +import com.android.tools.apk.analyzer.AndroidApplicationInfo +import com.android.tools.apk.analyzer.ApkSizeCalculator +import com.android.tools.apk.analyzer.Archives +import com.android.tools.apk.analyzer.dex.DexFileStats +import com.android.tools.apk.analyzer.dex.DexFiles +import com.android.tools.apk.analyzer.internal.ApkDiffEntry +import com.android.tools.apk.analyzer.internal.ApkFileByFileDiffParser +import java.io.File +import java.util.zip.ZipFile +import javax.swing.tree.DefaultMutableTreeNode + +/** + * General utility for interfacing with apkanalyzer's internal library. This implementation + * generally behaves like the apkanalyzer CLI implementation with some simplification. Note that the + * actual apkanalyzer tool can't be used since it isn't exported by android_sdk_repository. + * + * @property aapt2Client the [Aapt2Client] needed to access aapt2 by some routines + */ +class ApkAnalyzerClient(private val aapt2Client: Aapt2Client) { + private val apkSizeCalculator by lazy { ApkSizeCalculator.getDefault() } + + // CLI reference: https://developer.android.com/studio/command-line/apkanalyzer. + + /** Returns the file size of the specified APK as similarly reported by apkanalyzer. */ + fun computeFileSize(inputApkPath: String): Long = + apkSizeCalculator.getFullApkRawSize(File(inputApkPath).toPath()) + + /** + * Returns the estimated download size of the specified APK as similarly reported by apkanalyzer. + */ + fun computeDownloadSize(inputApkPath: String): Long = + apkSizeCalculator.getFullApkDownloadSize(File(inputApkPath).toPath()) + + /** + * Returns the list of required features of the specified APK as similarly reported by + * apkanalyzer. + */ + fun computeFeatures(inputApkPath: String): List { + val apkInfo = AndroidApplicationInfo.parseBadging(aapt2Client.dumpBadging(inputApkPath)) + return apkInfo.usesFeature.keys.toList() + } + + /** + * Returns a full comparison of the two specified APKs, similar to apkanalyzer. Note that each + * entry of the returned list represents a single line (the list is in the order of the comparison + * output). + */ + fun compare(inputApkPath1: String, inputApkPath2: String): List { + return Archives.open(File(inputApkPath1).toPath()).use { apkArchive1 -> + Archives.open(File(inputApkPath2).toPath()).use { apkArchive2 -> + walkParseTree(ApkFileByFileDiffParser.createTreeNode(apkArchive1, apkArchive2)) + } + } + } + + /** + * Returns the map of dex files to method counts contained within the specified APK file, as + * similarly reported by apkanalyzer. + */ + fun computeDexReferencesList(inputApkPath: String): Map { + return collectZipEntries(inputApkPath) { + it.endsWith(".dex") + }.mapValues { (_, dexFileContents) -> + DexFileStats.create(listOf(DexFiles.getDexFile(dexFileContents))).referencedMethodCount + } + } + + // Based on the apkanalyzer CLI implementation. + private fun walkParseTree(node: DefaultMutableTreeNode?): List { + return node?.let { + (node.userObject as? ApkDiffEntry)?.let { entry -> + val path = entry.path.toString() + when { + // Encountered a folder. Recurse down it in case its sizes aren't accurate (descendants + // may have changed). + path.endsWith("/") -> { + (0 until node.getChildCount()) + .map { node.getChildAt(it) } + .flatMap { walkParseTree(it as? DefaultMutableTreeNode) } + } + // Only record the entry if there's a difference. + entry.oldSize != entry.newSize -> { + listOf("${entry.oldSize}\t${entry.newSize}\t${entry.size}\t${entry.path}") + } + else -> null + } + } + } ?: listOf() + } + + private fun collectZipEntries( + inputZipFile: String, + namePredicate: (String) -> Boolean + ): Map { + return ZipFile(inputZipFile).use { zipFile -> + zipFile.entries() + .asSequence() + .filter { namePredicate(it.name) } + .associateBy { it.name } + .mapValues { (_, entry) -> zipFile.getInputStream(entry).readBytes() } + } + } +} diff --git a/scripts/src/java/org/oppia/android/scripts/apkstats/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/apkstats/BUILD.bazel new file mode 100644 index 00000000000..7730a9c084a --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/apkstats/BUILD.bazel @@ -0,0 +1,64 @@ +""" +Libraries corresponding to developer scripts that help with computing APK & AAB stats. +""" + +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") + +kt_jvm_library( + name = "compute_aab_differences_lib", + testonly = True, + srcs = [ + "ComputeAabDifferences.kt", + ], + visibility = ["//scripts:oppia_script_binary_visibility"], + deps = [ + ":aapt2_client", + ":apk_analyzer_client", + ":bundle_tool_client", + "//scripts/src/java/org/oppia/android/scripts/common:android_build_sdk_properties", + ], +) + +kt_jvm_library( + name = "aapt2_client", + testonly = True, + srcs = [ + "Aapt2Client.kt", + ], + visibility = ["//scripts:oppia_script_test_visibility"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/common:command_executor", + ], +) + +kt_jvm_library( + name = "apk_analyzer_client", + testonly = True, + srcs = [ + "ApkAnalyzerClient.kt", + ], + visibility = ["//scripts:oppia_script_test_visibility"], + runtime_deps = [ + "//third_party:com_google_archivepatcher", + ], + deps = [ + ":aapt2_client", + "//third_party:com_android_tools_apkparser_apkanalyzer", + ], +) + +kt_jvm_library( + name = "bundle_tool_client", + testonly = True, + srcs = [ + "BundleToolClient.kt", + ], + visibility = ["//scripts:oppia_script_test_visibility"], + runtime_deps = [ + "//third_party:android_bundletool", + "//third_party:com_google_guava_guava_jre", + ], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/common:command_executor", + ], +) diff --git a/scripts/src/java/org/oppia/android/scripts/apkstats/BundleToolClient.kt b/scripts/src/java/org/oppia/android/scripts/apkstats/BundleToolClient.kt new file mode 100644 index 00000000000..5c7ed73d26a --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/apkstats/BundleToolClient.kt @@ -0,0 +1,130 @@ +package org.oppia.android.scripts.apkstats + +import org.oppia.android.scripts.common.CommandExecutor +import org.oppia.android.scripts.common.CommandExecutorImpl +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import java.io.File +import java.util.zip.ZipFile + +/** + * General utility for interfacing with bundletool in the local system at the specified working + * directory path. + * + * @property workingDirectoryPath the path to the working directory in which instances of bundletool + * should be executed + * @param scriptBgDispatcher the [ScriptBackgroundCoroutineDispatcher] to be used for running the + * bundletool command + * @property commandExecutor the [CommandExecutor] to use when accessing bundletool + */ +class BundleToolClient( + private val workingDirectoryPath: String, + scriptBgDispatcher: ScriptBackgroundCoroutineDispatcher, + private val commandExecutor: CommandExecutor = CommandExecutorImpl(scriptBgDispatcher) +) { + private val workingDirectory by lazy { File(workingDirectoryPath) } + + // CLI reference: https://developer.android.com/studio/command-line/bundletool. + + /** + * Builds & extracts configuration-specific APKs corresponding to the specified Android app + * bundle. + * + * @param inputBundlePath the AAB from which to extract APKs + * @param outputApksListPath the destination .apks file intermediary used to extract the APKs + * @param outputApkDirPath the destination directory in which the extracted APKs should be written + * @return the list of [File]s where each corresponds to one of the computed APKs + */ + fun buildApks( + inputBundlePath: String, + outputApksListPath: String, + outputApkDirPath: String + ): List { + val destDir = File(outputApkDirPath) + return buildApkList(inputBundlePath, outputApksListPath).use { zipFile -> + val apkEntries = + zipFile.entries() + .asSequence() + .filter { !it.isDirectory && it.name.endsWith(".apk", ignoreCase = true) } + return@use apkEntries.map { entry -> + val outputApkFile = File(destDir, entry.name.substringAfter('/')) + zipFile.extractTo(entry.name, outputApkFile.absolutePath) + }.toList() + } + } + + /** + * Builds and returns the file to a universal APK built in the specified output directory path and + * built according to the specified Android app bundle. + */ + fun buildUniversalApk(inputBundlePath: String, outputApkPath: String): File { + return buildApkList(inputBundlePath, "$outputApkPath.apks", "--mode=universal").use { zipFile -> + zipFile.extractTo("universal.apk", outputApkPath) + } + } + + private fun buildApkList( + inputBundlePath: String, + outputApksListPath: String, + vararg additionalArgs: String + ): ZipFile { + executeBundleToolCommand( + "build-apks", "--bundle=$inputBundlePath", "--output=$outputApksListPath", *additionalArgs + ) + return ZipFile(File(outputApksListPath)) + } + + private fun executeBundleToolCommand(vararg arguments: String): List { + // Reference for retaining the classpath: https://stackoverflow.com/a/4330928. Note that this + // approach is needed vs. using reflection since bundle tool seems to not allow multiple + // subsequent calls (so each call must be in a new process). + val result = + commandExecutor.executeCommand( + workingDirectory, + "java", + "-classpath", + computeAbsoluteClasspath(), + "com.android.tools.build.bundletool.BundleToolMain", + *arguments + ) + check(result.exitCode == 0) { + "Expected zero exit code (not ${result.exitCode}) for command: ${result.command}." + + "\nStandard output:\n${result.output.joinToString("\n")}" + + "\nError output:\n${result.errorOutput.joinToString("\n")}" + } + return result.output + } + + private companion object { + private val currentDirectory by lazy { File(".") } + + private fun ZipFile.extractTo(entryName: String, destPath: String): File { + val destFile = File(destPath) + destFile.outputStream().use { outputStream -> + getInputStream(getEntry(entryName)).copyTo(outputStream) + } + return destFile + } + + private fun computeAbsoluteClasspath(): String { + val classpath = System.getProperty("java.class.path") ?: "." + val classpathComponents = classpath.split(":") + return classpathComponents.map { + it.convertToAbsolutePath() + }.filterNot { + it.isAndroidDependencyToOmit() + }.joinToString(":") + } + + private fun String.convertToAbsolutePath(): String { + return File(currentDirectory, this).absolutePath + } + + private fun String.isAndroidDependencyToOmit(): Boolean { + // This is a hacky way to work around the classpath actually pulling in two versions of + // Guava: Android & JRE. Bundle tool requires the JRE version, and there's no obvious way to + // separate out the Maven dependencies without risking duplicate versions & automatic conflict + // resolution. + return "guava_android" in File(this).absolutePath + } + } +} diff --git a/scripts/src/java/org/oppia/android/scripts/apkstats/ComputeAabDifferences.kt b/scripts/src/java/org/oppia/android/scripts/apkstats/ComputeAabDifferences.kt new file mode 100644 index 00000000000..a93dd9ad791 --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/apkstats/ComputeAabDifferences.kt @@ -0,0 +1,798 @@ +package org.oppia.android.scripts.apkstats + +import org.oppia.android.scripts.common.AndroidBuildSdkProperties +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import java.io.File +import java.io.PrintStream +import java.nio.file.Files +import java.util.Locale +import java.util.StringTokenizer +import java.util.zip.ZipFile +import kotlin.math.absoluteValue + +// TODO(#1719): Add support for showing count & itemization of modified files/resources (vs. just +// new/removed). + +/** + * The main entrypoint for analyzing different builds of the app and computing stat differences. + * + * Usage: + * bazel run //scripts:compute_aab_differences -- \\ + * \\ + * [ \\ + * ] ... + * + * Arguments: + * - path_to_brief_summary_output_file: path to the file that will contain a brief difference + * summary. + * - path_to_full_summary_output_file: path to the file that will contain a more detailed difference + * summary. + * - One or more triplets containing: + * - build_flavor: the flavor of the build corresponding to this quartet (e.g. alpha). + * - path_to_flavor_input_without_changes: path to the built AAB for this flavor that doesn't + * contain the changes to analyze (e.g. built on develop). + * - path_to_flavor_input_with_changes: path to the built AAB for this flavor that includes the + * changes to analyze. + * + * Example: + * bazel run //scripts:compute_aab_differences -- \\ + * $(pwd)/brief_build_summary.log $(pwd)/full_build_summary.log \\ + * dev $(pwd)/dev_no_changes.aab $(pwd)/dev_with_changes.aab \\ + * alpha $(pwd)/alpha_no_changes.aab $(pwd)/alpha_with_changes.aab + */ +fun main(vararg args: String) { + val outputSummaryFilePath = args[0] + val outputFullSummaryFilePath = args[1] + + val remainingArgCount = args.size - 2 + check(remainingArgCount > 0 && (remainingArgCount % 3) == 0) { + "Expected at least 1 triplet entry of the form: " + } + val profiles = + args.drop(2).chunked(3).map { (flavor, aabNoChangesPath, aabWithChangesPath) -> + ComputeAabDifferences.AabProfile( + buildFlavor = flavor, + oldAabFilePath = aabNoChangesPath, + newAabFilePath = aabWithChangesPath + ) + } + + println("NOTE: Computing ${profiles.size} build flavor stats profiles.") + + val workingDirectoryPath = "." + val sdkProperties = AndroidBuildSdkProperties() + val buildStats = ScriptBackgroundCoroutineDispatcher().use { scriptBgDispatcher -> + val computer = ComputeAabDifferences(workingDirectoryPath, sdkProperties, scriptBgDispatcher) + return@use computer.computeBuildStats(*profiles.toTypedArray()) + } + PrintStream(outputSummaryFilePath).use { stream -> + buildStats.writeSummariesTo(stream, longSummary = false) + } + PrintStream(outputFullSummaryFilePath).use { stream -> + buildStats.writeSummariesTo(stream, longSummary = true) + } +} + +/** Utility to compute the build differences between sets of AABs. */ +class ComputeAabDifferences( + workingDirectoryPath: String, + sdkProperties: AndroidBuildSdkProperties, + scriptBgDispatcher: ScriptBackgroundCoroutineDispatcher +) { + private val bundleToolClient by lazy { + BundleToolClient(workingDirectoryPath, scriptBgDispatcher) + } + private val aapt2Client by lazy { + Aapt2Client(workingDirectoryPath, sdkProperties.buildToolsVersion, scriptBgDispatcher) + } + private val apkAnalyzerClient by lazy { ApkAnalyzerClient(aapt2Client) } + + /** + * Returns the [BuildStats] for the provided set of [AabProfile]s. All profiles will be + * represented in the returned stats. + */ + fun computeBuildStats(vararg aabProfiles: AabProfile): BuildStats { + val aabStats = aabProfiles.associate { profile -> + profile.buildFlavor to computeAabStats(profile.oldAabFilePath, profile.newAabFilePath) + } + return BuildStats(aabStats) + } + + private fun computeAabStats(aabWithoutChangesPath: String, aabWithChangesPath: String): AabStats { + println( + "Computing AAB stats for AABs: $aabWithoutChangesPath (old) and $aabWithChangesPath (new)..." + ) + val parentDestDir = Files.createTempDirectory("apk_diff_stats_").toFile() + println("Using ${parentDestDir.absolutePath} as an intermediary working directory") + val destWithoutChanges = parentDestDir.newDirectory("without_changes").absolutePath + val destWithChanges = parentDestDir.newDirectory("with_changes").absolutePath + + val universalApkWithoutChanges = computeUniversalApk(aabWithoutChangesPath, destWithoutChanges) + val universalApkWithChanges = computeUniversalApk(aabWithChangesPath, destWithChanges) + + val (mainApkWithoutChanges, splitApksWithoutChanges) = + computeApksList(aabWithoutChangesPath, destWithoutChanges) + val (mainApkWithChanges, splitApksWithChanges) = + computeApksList(aabWithChangesPath, destWithChanges) + val splitApkStats = combineMaps( + splitApksWithoutChanges, splitApksWithChanges + ) { fileWithoutChangesPath, fileWithChangesPath -> + computeConfigurationStats(fileWithoutChangesPath, fileWithChangesPath) + } + val configurationsList = + DiffList(splitApksWithoutChanges.keys.toList(), splitApksWithChanges.keys.toList()) + + return AabStats( + universalApkStats = computeConfigurationStats( + universalApkWithoutChanges, universalApkWithChanges + ), + mainSplitApkStats = computeConfigurationStats( + mainApkWithoutChanges, mainApkWithChanges + ), + splitApkStats = splitApkStats, + configurationsList = configurationsList + ) + } + + private fun computeUniversalApk(inputAabPath: String, destDir: String): String { + println("Generating universal APK for: $inputAabPath") + val universalApkPath = File(destDir, "universal.apk").absolutePath + val universalApk = bundleToolClient.buildUniversalApk(inputAabPath, universalApkPath) + return universalApk.absolutePath + } + + private fun computeApksList( + inputAabPath: String, + destDir: String + ): Pair> { + println("Generating APK list for: $inputAabPath") + val apksListPath = File(destDir, "list.apks").absolutePath + val apkFiles = bundleToolClient.buildApks(inputAabPath, apksListPath, destDir) + val mainApkFilePath = apkFiles.first { "master" in it.name }.absolutePath + val apkFilePathMap = + apkFiles.filter { "master" !in it.name } + .associate { file -> + file.name.substringAfter("base-").substringBefore('.') to file.absolutePath + } + return mainApkFilePath to apkFilePathMap + } + + private fun computeConfigurationStats( + apkWithoutChangesPath: String?, + apkWithChangesPath: String? + ): ApkConfigurationStats { + println("Comparing APKs: $apkWithoutChangesPath and $apkWithChangesPath") + val fullComparison = if (apkWithoutChangesPath != null && apkWithChangesPath != null) { + apkAnalyzerClient.compare(apkWithoutChangesPath, apkWithChangesPath) + } else null + return ApkConfigurationStats( + fileSizeStats = computeFileSizeStats(apkWithoutChangesPath, apkWithChangesPath), + dexStats = computeDexStats(apkWithoutChangesPath, apkWithChangesPath), + manifestStats = computeManifestStats(apkWithoutChangesPath, apkWithChangesPath), + resourceStats = computeResourceStats(apkWithoutChangesPath, apkWithChangesPath), + assetStats = computeAssetStats(apkWithoutChangesPath, apkWithChangesPath), + completeFileDiff = fullComparison + ) + } + + private fun computeFileSizeStats( + apkWithoutChangesPath: String?, + apkWithChangesPath: String? + ): FileSizeStats { + println("Computing file size for: $apkWithoutChangesPath and $apkWithChangesPath") + val (fileSizeWithoutChanges, downloadSizeWithoutChanges) = if (apkWithoutChangesPath != null) { + apkAnalyzerClient.computeFileSize(apkWithoutChangesPath) to + apkAnalyzerClient.computeDownloadSize(apkWithoutChangesPath) + } else 0L to 0L + + println("Computing estimated download size for: $apkWithoutChangesPath and $apkWithChangesPath") + val (fileSizeWithChanges, downloadSizeWithChanges) = if (apkWithChangesPath != null) { + apkAnalyzerClient.computeFileSize(apkWithChangesPath) to + apkAnalyzerClient.computeDownloadSize(apkWithChangesPath) + } else 0L to 0L + + return FileSizeStats( + fileSize = DiffLong(oldValue = fileSizeWithoutChanges, newValue = fileSizeWithChanges), + downloadSize = DiffLong( + oldValue = downloadSizeWithoutChanges, newValue = downloadSizeWithChanges + ) + ) + } + + private fun computeDexStats( + apkWithoutChangesPath: String?, + apkWithChangesPath: String? + ): DexStats { + println("Computing dex method counts for: $apkWithoutChangesPath and $apkWithChangesPath") + val methodCountWithoutChanges = apkWithoutChangesPath?.let { apkPath -> + apkAnalyzerClient.computeDexReferencesList(apkPath).values.sum().toLong() + } ?: 0L + val methodCountWithChanges = apkWithChangesPath?.let { apkPath -> + apkAnalyzerClient.computeDexReferencesList(apkPath).values.sum().toLong() + } ?: 0L + return DexStats( + DiffLong(oldValue = methodCountWithoutChanges, newValue = methodCountWithChanges) + ) + } + + private fun computeManifestStats( + apkWithoutChangesPath: String?, + apkWithChangesPath: String? + ): ManifestStats { + println("Computing feature and permissions for: $apkWithoutChangesPath and $apkWithChangesPath") + val (featuresWithoutChanges, permissionsWithoutChanges) = apkWithoutChangesPath?.let { path -> + val features = apkAnalyzerClient.computeFeatures(path) + val rawPermissions = aapt2Client.dumpPermissions(path) + return@let features to extractPermissions(rawPermissions) + } ?: (listOf() to listOf()) + val (featuresWithChanges, permissionsWithChanges) = apkWithChangesPath?.let { path -> + val features = apkAnalyzerClient.computeFeatures(path) + val rawPermissions = aapt2Client.dumpPermissions(path) + return@let features to extractPermissions(rawPermissions) + } ?: (listOf() to listOf()) + + return ManifestStats( + features = DiffList(featuresWithoutChanges, featuresWithChanges), + permissions = DiffList(permissionsWithoutChanges, permissionsWithChanges) + ) + } + + private fun computeResourceStats( + apkWithoutChangesPath: String?, + apkWithChangesPath: String? + ): ResourceStats { + println("Computing resource maps for: $apkWithoutChangesPath and $apkWithChangesPath") + val resourcesWithoutChanges = apkWithoutChangesPath?.let { apkPath -> + extractResources(aapt2Client.dumpResources(apkPath)) + } ?: mapOf() + val resourcesWithChanges = apkWithChangesPath?.let { apkPath -> + extractResources(aapt2Client.dumpResources(apkPath)) + } ?: mapOf() + return ResourceStats(combineResourceMaps(resourcesWithoutChanges, resourcesWithChanges)) + } + + private fun computeAssetStats( + apkWithoutChangesPath: String?, + apkWithChangesPath: String? + ): AssetStats { + // Only consider top-level files in the assets/ folder. + println("Computing asset stats for: $apkWithoutChangesPath and $apkWithChangesPath") + return AssetStats( + DiffList( + apkWithoutChangesPath?.let(::File)?.extractAssetFileNamesFromApk() ?: listOf(), + apkWithChangesPath?.let(::File)?.extractAssetFileNamesFromApk() ?: listOf() + ) + ) + } + + /** + * Represents the AABs corresponding to a single build flavor. + * + * @property buildFlavor the name of the build flavor + * @property oldAabFilePath the path to the AAB build of this flavor that doesn't include changes + * to analyze + * @property newAabFilePath the path to the AAB build of this flavor that includes changes to + * analyze + */ + data class AabProfile( + val buildFlavor: String, + val oldAabFilePath: String, + val newAabFilePath: String + ) + + /** + * Represents the computed build stats for multiple build flavors. + * + * @property aabStats a map from build flavor to [AabStats] corresponding to a list of + * [AabProfile]s from and for which build stats were computed + */ + data class BuildStats(val aabStats: Map) { + /** + * Writes the build stats summary to [stream] with a configurable length using [longSummary]. + */ + fun writeSummariesTo(stream: PrintStream, longSummary: Boolean) { + stream.println("# APK & AAB differences analysis") + if (!longSummary) { + stream.println( + "Note that this is a summarized snapshot. See the CI artifacts for detailed" + + " differences." + ) + } + val itemLimit = if (!longSummary) 5 else Int.MAX_VALUE + aabStats.forEach { (buildFlavor, stats) -> + stream.println() + stats.writeSummaryTo(stream, buildFlavor, itemLimit, longSummary) + } + } + } + + /** + * Difference stats between two AABs for a single build flavor. + * + * @property universalApkStats stats corresponding to the before & after universal APKs built for + * the considered build flavor + * @property mainSplitApkStats stats corresponding to the base APK of the compared AABs for the + * considered build flavor + * @property splitApkStats a map from configuration name to the compared stats for each split APK + * of the compared AABs for the considered build flavor + * @property configurationsList a difference list to compare the available AAB configurations for + * both AABs of the considered build flavor + */ + data class AabStats( + val universalApkStats: ApkConfigurationStats, + val mainSplitApkStats: ApkConfigurationStats, + val splitApkStats: Map, + val configurationsList: DiffList + ) { + /** + * Writes the stats summary between two AABs to [stream]. + * + * @param buildFlavor the build flavor corresponding to the compared AABs + * @param itemLimit the max number of items to include in expanded lists (only used if + * [longSummary] is true) + * @param longSummary whether to print a more detailed summary + */ + fun writeSummaryTo( + stream: PrintStream, + buildFlavor: String, + itemLimit: Int, + longSummary: Boolean + ) { + val buildFlavorTitle = buildFlavor.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() + } + stream.println("## $buildFlavorTitle") + stream.println() + + if (!longSummary) { + stream.println("
Expand to see flavor specifics") + stream.println() + } + + stream.println("### Universal APK") + universalApkStats.writeSummaryTo(stream, itemize = true, longSummary, itemLimit) + + stream.println("### AAB differences") + if (!longSummary) { + stream.println("
Expand to see AAB specifics") + stream.println() + } + stream.println("Supported configurations:") + configurationsList.forEach { (type, configuration) -> + stream.println("- $configuration (${type.humanReadableName})") + } + stream.println() + + stream.println("#### Base APK") + mainSplitApkStats.writeSummaryTo(stream, itemize = longSummary, longSummary, itemLimit) + + splitApkStats.forEach { (configuration, stats) -> + stream.println() + stream.println("#### Configuration $configuration") + stats.writeSummaryTo(stream, itemize = longSummary, longSummary, itemLimit) + } + if (!longSummary) stream.println("
") + } + } + + /** + * Enumerated stats demonstrating the high-level differences between two APKs. + * + * @property fileSizeStats file stats comparison between the two APKs + * @property dexStats dex stats comparison between the two APKs + * @property manifestStats manifest stats comparison between the two APKs + * @property resourceStats resource stats comparison between the two APKs + * @property assetStats asset stats comparison between the two APKs + * @property completeFileDiff the lines of the full comparison output between the two APKs, or + * null if none is computed for this APK + */ + data class ApkConfigurationStats( + val fileSizeStats: FileSizeStats, + val dexStats: DexStats, + val manifestStats: ManifestStats, + val resourceStats: ResourceStats, + val assetStats: AssetStats, + val completeFileDiff: List? + ) { + /** + * Writes the stats summary between two APKs to [stream]. + * + * @param itemLimit the max number of items to include in expanded lists + * @param longSummary whether extra details should be included in the summary + * @param itemize whether to expand lists of items + */ + fun writeSummaryTo( + stream: PrintStream, + itemize: Boolean, + longSummary: Boolean, + itemLimit: Int + ) { + fileSizeStats.writeTo(stream, itemize) + if (itemize) stream.println() + + dexStats.writeTo(stream, itemize) + if (itemize) stream.println() + + manifestStats.writeTo(stream, itemize, itemLimit) + if (itemize) stream.println() + + // Resources stats always has an extra blank newline (if there's anything to write) to + // ensure that following lines aren't included as part of the list that's always written for + // resources. + if (resourceStats.writeTo(stream, itemize, itemLimit)) { + stream.println() + } + + assetStats.writeTo(stream, itemize, itemLimit) + if (itemize) stream.println() + + if (longSummary) { + stream.println("*Detailed file differences:*") + if (completeFileDiff != null) { + completeFileDiff.forEach(stream::println) + } else stream.println("APK doesn't exist.") + stream.println() + } + } + } + + /** + * File size stats between two APKs. + * + * @property fileSize the difference in file size between the two APKs + * @property downloadSize the difference in estimated download size between the two APKs. Note + * that the download size for an AAB is likely the base APK download size + the matching + * configuration split APK download size (though this is again an estimate and may vary). + */ + data class FileSizeStats(val fileSize: DiffLong, val downloadSize: DiffLong) { + /** + * Writes the file stats summary between two APKs to [stream]. + * + * @param itemize whether to expand lists of items + */ + fun writeTo(stream: PrintStream, itemize: Boolean) { + fileSize.writeBytesTo(stream, "APK file size") + if (itemize) { + stream.println() + } + downloadSize.writeBytesTo(stream, "APK download size (estimated)") + } + } + + /** + * Dex stats between two APKs. + * + * @property methodCount the difference in total dex methods between the two APks. These + * correspond to declared methods in Kotlin that are being compiled and included in the APK's + * dex files. + */ + data class DexStats(val methodCount: DiffLong) { + /** + * Writes the dex stats summary between two APKs to [stream]. + * + * @param itemize whether to expand lists of items + */ + fun writeTo(stream: PrintStream, itemize: Boolean) { + if (itemize || methodCount.hasDifference()) { + methodCount.writeCountTo(stream, "Method count") + } + } + } + + /** + * Manifest stats between two APks. + * + * @property features the difference list of features between the two APks. These correspond to + * properties that affect who has access to install the app from the Play Store. + * @property permissions the difference list of permissions required between the two APKs. This + * will include both permissions automatically granted by the system and those that require + * user consent on L+ devices. + */ + data class ManifestStats( + val features: DiffList, + val permissions: DiffList + ) { + /** + * Writes the manifest stats summary between two APKs to [stream]. + * + * @param itemLimit the max number of items to include in expanded lists + * @param itemize whether to expand lists of items + */ + fun writeTo(stream: PrintStream, itemize: Boolean, itemLimit: Int) { + if (itemize || features.hasDifference()) { + features.writeTo(stream, "Features", itemize, itemLimit) + if (itemize) { + stream.println() + } + } + if (itemize || permissions.hasDifference()) { + permissions.writeTo(stream, "Permissions", itemize, itemLimit) + } + } + } + + /** + * Resource stats between two APKs. Note that resources include anything defined by Android as a + * resource, and may not necessarily correspond to specific files (e.g. dimensions and strings are + * considered separate resources despite generally being grouped in a few files). + * + * @property resources map from resource type to difference lists of the resource names compared + * between the two APKs. Note that if one APK is missing a certain resource type, the other + * APK will have an empty list within its [DiffList]. + */ + data class ResourceStats( + val resources: Map> + ) { + /** + * Writes the resource stats summary between two APKs to [stream]. + * + * @param itemLimit the max number of items to include in expanded lists + * @param itemize whether to expand lists of items + * @return whether anything was printed + */ + fun writeTo(stream: PrintStream, itemize: Boolean, itemLimit: Int): Boolean { + val totalOldCount = resources.values.sumOf { it.oldCount } + val totalNewCount = resources.values.sumOf { it.newCount } + val totalDifference = totalNewCount - totalOldCount + if (itemize || totalDifference != 0) { + stream.println( + "Resources: $totalOldCount (old), $totalNewCount (new)," + + " **${totalDifference.absoluteValue}** (${totalDifference.convertToDiffString()})" + ) + resources.forEach { (typeName, resourcesList) -> + if (itemize || resourcesList.hasDifference()) { + stream.print("- ") + resourcesList.writeTo( + stream, + linePrefix = typeName.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() + }, + itemize, itemLimit, listIndentation = 2 + ) + } + } + return true + } + return false + } + } + + /** + * Asset stats between two APKs. Note that for reporting simplicity, only immediate assets are + * considered (i.e. those that are direct children of the assets/ directory). + * + * @property assets the difference list between the assets from both APKs + */ + data class AssetStats(val assets: DiffList) { + /** + * Writes the asset stats summary between two APKs to [stream]. + * + * @param itemLimit the max number of items to include in expanded lists + * @param itemize whether to expand lists of items + */ + fun writeTo(stream: PrintStream, itemize: Boolean, itemLimit: Int) { + if (itemize || assets.hasDifference()) { + assets.writeTo(stream, "Lesson assets", itemize, itemLimit) + } + } + } + + /** + * A difference between two long values. + * + * @property oldValue the [Long] value corresponding to the build without changes + * @property newValue the [Long] value corresponding to the build with changes + */ + data class DiffLong(val oldValue: Long, val newValue: Long) { + /** The difference between the two values. */ + val difference: Long by lazy { newValue - oldValue } + + /** Returns whether the two values represented by this difference are actually different. */ + fun hasDifference(): Boolean = difference != 0L + } + + /** + * A difference between two [List]s. + * + * Note that rather than storing a list of [T] values, this stores a list of [DiffEntry]s to + * catalog similarities between the two lists. Note that this list's order is not guaranteed + * relative to the orders of either [oldList] or [newList]. + * + * Finally, this list may be used in cases when the values can guarantee equivalence (such a + * strings), but may not represent cases where those values correspond to files or properties + * whose values have changed (such as string resources). + * + * @property oldList the [List] of values corresponding to the build without changes + * @property newList the [List] of values corresponding to the build with changes + */ + class DiffList( + private val oldList: List, + private val newList: List + ) : AbstractList>() { + private val oldSet by lazy { oldList.toSet() } + private val newSet by lazy { newList.toSet() } + private val combined by lazy { oldSet + newSet } + private val processedEntries by lazy { processDiffs() } + + /** The number of tracked elements corresponding to the build without changes. */ + val oldCount: Int by lazy { oldSet.size } + + /** The number of tracked elements corresponding to the build with changes. */ + val newCount: Int by lazy { newSet.size } + + /** The difference in element count between the two tracked lists. */ + val countDifference: Int by lazy { newCount - oldCount } + + /** Returns whether the two tracked lists have different counts. */ + fun hasDifference(): Boolean = countDifference != 0 + + override val size: Int + get() = processedEntries.size + + override fun get(index: Int): DiffEntry = processedEntries[index] + + private fun processDiffs(): List> { + return combined.map { consideredValue -> + val inOldList = consideredValue in oldSet + val inNewList = consideredValue in newSet + val diffType = if (!inOldList && inNewList) { + DiffType.NEW_ENTRY + } else if (inOldList && !inNewList) { + DiffType.REMOVED_ENTRY + } else DiffType.SAME_ENTRY + return@map DiffEntry(diffType, consideredValue) + } + } + + /** + * Represents a difference between an old & new element in [DiffList]. + * + * @property humanReadableName the human-readable name corresponding to this type + */ + enum class DiffType(val humanReadableName: String) { + /** Represents two entries that are present in both lists. */ + SAME_ENTRY("same"), + + /** Represents an entry only present in the new list. */ + NEW_ENTRY("added"), + + /** Represents an entry only present in the old list. */ + REMOVED_ENTRY("removed") + } + + /** + * Corresponds to an entry within [DiffList]. + * + * @property type the [DiffType] corresponding to this value + * @property value the value that's present in one or both lists (depending on [type]) + */ + data class DiffEntry(val type: DiffType, val value: T) + } + + private companion object { + private fun File.newDirectory(name: String): File { + return File(this, name).also { it.mkdir() } + } + + private fun combineMaps( + oldMap: Map, + newMap: Map, + combineValue: (IV?, IV?) -> OV + ): Map { + val allKeys = oldMap.keys + newMap.keys + return allKeys.map { key -> + return@map key to combineValue(oldMap[key], newMap[key]) + }.toMap() + } + + private fun extractPermissions(permissionDump: List): List { + return permissionDump.filter { line -> + "name=" in line + }.map { line -> + line.substringAfter("name='").substringBefore('\'') + } + } + + private fun combineResourceMaps( + oldResourceMap: Map>, + newResourceMap: Map> + ): Map> { + return combineMaps(oldResourceMap, newResourceMap) { oldResources, newResources -> + DiffList(oldResources ?: listOf(), newResources ?: listOf()) + } + } + + private fun extractResources(resourceDump: List): Map> { + val resourceMap = mutableMapOf>() + lateinit var currentResourceList: MutableList + for (line in resourceDump) { + val tokenizer = StringTokenizer(line) + if (!tokenizer.hasMoreTokens()) continue + when (tokenizer.nextToken()) { + "type" -> { + val typeName = tokenizer.nextToken() + currentResourceList = mutableListOf() + resourceMap[typeName] = currentResourceList + } + "resource" -> { + tokenizer.nextToken() // Skip the ID. + val resourceName = tokenizer.nextToken() + currentResourceList.add(resourceName) + } + // Otherwise, skip it since it's details about the previous resource or top-level + // information. + } + } + return resourceMap + } + + private fun File.extractAssetFileNamesFromApk(): List { + return ZipFile(this).use { zipFile -> + zipFile.entries().asSequence().filter { entry -> + "assets/" in entry.name && "/" !in entry.name.substringAfter("assets/") + }.map { it.name.substringAfter("assets/") }.toList() + } + } + + private fun DiffList.writeTo( + stream: PrintStream, + linePrefix: String, + itemize: Boolean, + itemLimit: Int, + listIndentation: Int = 0 + ) { + val indent = " ".repeat(listIndentation) + stream.print( + "$linePrefix: $oldCount (old), $newCount (new)," + + " **${countDifference.absoluteValue}** (${countDifference.convertToDiffString()})" + ) + if (itemize && hasDifference()) { + stream.println(":") + val newOldAssets = filter { it.type != DiffList.DiffType.SAME_ENTRY } + newOldAssets.take(itemLimit).forEach { (type, assetName) -> + stream.println("$indent- $assetName (${type.humanReadableName})") + } + if (newOldAssets.size > itemLimit) { + val remaining = newOldAssets.size - itemLimit + stream.println("$indent- And $remaining other${if (remaining > 1) "s" else ""}") + } + } else stream.println() + } + + private fun DiffLong.writeCountTo(stream: PrintStream, linePrefix: String) { + stream.println( + "$linePrefix: $oldValue (old), $newValue (new)," + + " **${difference.absoluteValue}** (${difference.convertToDiffString()})" + ) + } + + private fun DiffLong.writeBytesTo(stream: PrintStream, linePrefix: String) { + stream.println( + "$linePrefix: ${oldValue.formatAsBytes()} (old), ${newValue.formatAsBytes()} (new)," + + " **${difference.formatAsBytes()}** (${difference.convertToDiffString()})" + ) + } + + private fun Long.formatAsBytes(): String { + val magnitude = absoluteValue + return when { + magnitude < 10_000L -> "$magnitude bytes" + magnitude < 10_000_000L -> "${magnitude / 1024} KiB" + magnitude < 10_000_000_000L -> "${magnitude / (1024 * 1024)} MiB" + else -> "${magnitude / (1024 * 1024 * 1024)} GiB" + } + } + + private fun Int.convertToDiffString() = when { + this > 0 -> "Added" + this < 0 -> "Removed" + else -> "No change" + } + + private fun Long.convertToDiffString() = when { + this > 0 -> "Added" + this < 0 -> "Removed" + else -> "No change" + } + } +} diff --git a/scripts/src/java/org/oppia/android/scripts/common/AndroidBuildSdkProperties.kt b/scripts/src/java/org/oppia/android/scripts/common/AndroidBuildSdkProperties.kt new file mode 100644 index 00000000000..a85a11bfdcc --- /dev/null +++ b/scripts/src/java/org/oppia/android/scripts/common/AndroidBuildSdkProperties.kt @@ -0,0 +1,32 @@ +package org.oppia.android.scripts.common + +import java.io.InputStream +import java.util.Properties + +/** Represents Android SDK properties available at build time. */ +class AndroidBuildSdkProperties { + private val androidSdkProperties by lazy { + Properties().also { props -> + AndroidBuildSdkProperties::class.java.loadResource(SDK_INFO_PROPERTIES_PATH).use(props::load) + } + } + + /** The version of the Android SDK used for building. */ + val buildSdkVersion: Int + get() = androidSdkProperties.getExpectedProperty("build_sdk_version").toInt() + + /** The version of Android CLI tools used for building. */ + val buildToolsVersion: String + get() = androidSdkProperties.getExpectedProperty("build_tools_version") + + private companion object { + private const val SDK_INFO_PROPERTIES_PATH = "sdk_info.properties" + + private fun Class<*>.loadResource(name: String): InputStream = + checkNotNull(getResourceAsStream(name)) { "Failed to find resource: $name." } + + private fun Properties.getExpectedProperty(key: String): String { + return checkNotNull(getProperty(key)) { "Expected property to be present: $key" } + } + } +} diff --git a/scripts/src/java/org/oppia/android/scripts/common/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/common/BUILD.bazel index b9f8c452efb..b4942de8721 100644 --- a/scripts/src/java/org/oppia/android/scripts/common/BUILD.bazel +++ b/scripts/src/java/org/oppia/android/scripts/common/BUILD.bazel @@ -4,6 +4,18 @@ generic operations. """ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +load("//:build_vars.bzl", "BUILD_SDK_VERSION", "BUILD_TOOLS_VERSION") + +kt_jvm_library( + name = "android_build_sdk_properties", + testonly = True, + srcs = [ + "AndroidBuildSdkProperties.kt", + ], + resource_strip_prefix = "scripts/src/java", + resources = [":_sdk_info_properties"], + visibility = ["//scripts:oppia_script_library_visibility"], +) kt_jvm_library( name = "bazel_client", @@ -88,3 +100,13 @@ kt_jvm_library( "//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-core", ], ) + +genrule( + name = "_sdk_info_properties", + srcs = [], + outs = ["sdk_info.properties"], + cmd = """ + echo build_sdk_version={0} > \"$@\" + echo build_tools_version={1} >> \"$@\" + """.format(BUILD_SDK_VERSION, BUILD_TOOLS_VERSION), +) diff --git a/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt b/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt index 01d59a6f505..9aede2fd16f 100644 --- a/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt +++ b/scripts/src/java/org/oppia/android/scripts/common/BazelClient.kt @@ -84,7 +84,7 @@ class BazelClient(private val rootDirectory: File, private val commandExecutor: } /** - * Returns the list of direct and indirect maven third-party dependencies on which the specified + * Returns the list of direct and indirect Maven third-party dependencies on which the specified * binary depends. */ fun retrieveThirdPartyMavenDepsListForBinary(binaryTarget: String): List { diff --git a/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesListCheck.kt b/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesListCheck.kt index 4db0c1d7f45..d6b5ff1d985 100644 --- a/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesListCheck.kt +++ b/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesListCheck.kt @@ -8,7 +8,7 @@ import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher import org.oppia.android.scripts.proto.MavenDependency /** - * The main entrypoint for verifying the list of third-party maven dependencies in + * The main entrypoint for verifying the list of third-party Maven dependencies in * maven_dependencies.textproto is up-to-date. * * Usage: @@ -18,7 +18,7 @@ import org.oppia.android.scripts.proto.MavenDependency * * Arguments: * - path_to_root: directory path to the root of the Oppia Android repository. - * - path_to_maven_install_json: relative path to the maven_install.json file. + * - path_to_maven_install_json: relative path to the Maven installation manifest file. * - path_to_maven_dependencies_pb: relative path to the maven_dependencies.pb file. * * Example: @@ -41,7 +41,7 @@ class MavenDependenciesListCheck( private val commandExecutor: CommandExecutor = CommandExecutorImpl(scriptBgDispatcher) ) { /** - * Verifies that the list of third-party maven dependencies in maven_dependnecies.textproto is + * Verifies that the list of third-party Maven dependencies in maven_dependnecies.textproto is * up-to-date. */ fun main(args: Array) { diff --git a/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesRetriever.kt b/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesRetriever.kt index 78f2e20dda7..43f0193047c 100644 --- a/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesRetriever.kt +++ b/scripts/src/java/org/oppia/android/scripts/license/MavenDependenciesRetriever.kt @@ -24,7 +24,7 @@ import javax.xml.parsers.DocumentBuilderFactory private const val MAVEN_PREFIX = "@maven//:" -/** Helper to compile the third-party maven dependencies list for Oppia Android. */ +/** Helper to compile the third-party Maven dependencies list for Oppia Android. */ class MavenDependenciesRetriever( private val rootPath: String, private val mavenArtifactPropertyFetcher: MavenArtifactPropertyFetcher, @@ -167,7 +167,7 @@ class MavenDependenciesRetriever( } /** - * Retrieves the list of maven dependencies from maven_dependencies.textproto. + * Retrieves the list of Maven dependencies from maven_dependencies.textproto. * * @param pathToPbFile path to the pb file to be parsed * @return list of dependencies @@ -184,7 +184,7 @@ class MavenDependenciesRetriever( * files. * * @param finalDependenciesList list of dependencies that is obtained by the intersection of - * the list generated by Bazel Query and the list generated from maven_install.json + * the list generated by Bazel Query and the list generated from the Maven install manifest * @return a [Deferred] of a list of [MavenListDependency] that has dependencies with licenses * extracted from their POM files */ @@ -233,9 +233,9 @@ class MavenDependenciesRetriever( } /** - * Parses the maven_install.json file to compile the list of maven dependencies. + * Parses the Maven install manifest file to compile the list of Maven dependencies. * - * @param pathToMavenInstall path to the maven_install.json file + * @param pathToMavenInstall path to the Maven install manifest file * @param bazelQueryDepsNames list of dependency names obtained from the bazel query * @return a [Deferred] of a list of [MavenListDependency]s that contains the artifact name and a * URL that is used to obtain the URL of the POM file of the dependency diff --git a/scripts/src/java/org/oppia/android/scripts/maven/GenerateMavenDependenciesList.kt b/scripts/src/java/org/oppia/android/scripts/maven/GenerateMavenDependenciesList.kt index 3d1292b94d1..f58b82dac66 100644 --- a/scripts/src/java/org/oppia/android/scripts/maven/GenerateMavenDependenciesList.kt +++ b/scripts/src/java/org/oppia/android/scripts/maven/GenerateMavenDependenciesList.kt @@ -20,7 +20,7 @@ import org.oppia.android.scripts.proto.MavenDependencyList * * Arguments: * - path_to_directory_root: directory path to the root of the Oppia Android repository. - * - path_to_maven_install_json: relative path to the maven_install.json file. + * - path_to_maven_install_json: relative path to the Maven install manifest file. * - path_to_maven_dependencies_textproto: relative path to the maven_dependencies.textproto * - path_to_maven_dependencies_pb: relative path to the maven_dependencies.pb file. * Example: @@ -41,7 +41,7 @@ class GenerateMavenDependenciesList( private val commandExecutor: CommandExecutor = CommandExecutorImpl(scriptBgDispatcher) ) { /** - * Compiles a list of third-party maven dependencies along with their license links on + * Compiles a list of third-party Maven dependencies along with their license links on * which Oppia Android depends and write them in maven_dependencies.textproto. */ fun main(args: Array) { diff --git a/scripts/src/java/org/oppia/android/scripts/maven/RetrieveLicenseTexts.kt b/scripts/src/java/org/oppia/android/scripts/maven/RetrieveLicenseTexts.kt index c7ce282c995..174980cb0cd 100644 --- a/scripts/src/java/org/oppia/android/scripts/maven/RetrieveLicenseTexts.kt +++ b/scripts/src/java/org/oppia/android/scripts/maven/RetrieveLicenseTexts.kt @@ -93,7 +93,7 @@ class RetrieveLicenseTexts( } /** - * Retrieve the list of maven dependencies from maven_dependencies.textproto. + * Retrieve the list of Maven dependencies from maven_dependencies.textproto. * * @param pathToPbFile path to the pb file to be parsed * @return list of [MavenDependency]s diff --git a/scripts/src/java/org/oppia/android/scripts/maven/model/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/maven/model/BUILD.bazel index 9e0180a3807..4ad35ca52ee 100644 --- a/scripts/src/java/org/oppia/android/scripts/maven/model/BUILD.bazel +++ b/scripts/src/java/org/oppia/android/scripts/maven/model/BUILD.bazel @@ -1,5 +1,5 @@ """ -Data structures corresponding to parsing maven_install.json. +Data structures corresponding to parsing Maven installation manifests. """ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") diff --git a/scripts/src/javatests/org/oppia/android/scripts/apkstats/Aapt2ClientTest.kt b/scripts/src/javatests/org/oppia/android/scripts/apkstats/Aapt2ClientTest.kt new file mode 100644 index 00000000000..bb19478ac27 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/apkstats/Aapt2ClientTest.kt @@ -0,0 +1,62 @@ +package org.oppia.android.scripts.apkstats + +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.oppia.android.scripts.common.AndroidBuildSdkProperties +import org.oppia.android.scripts.common.CommandExecutorImpl +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.testing.assertThrows +import java.util.concurrent.TimeUnit + +/** + * Tests for [Aapt2Client]. + * + * Note that this test executes real commands on the local filesystem. + */ +// Same parameter value: helpers reduce test context, even if they are used by 1 test. +// Function name: test names are conventionally named with underscores. +@Suppress("SameParameterValue", "FunctionName") +class Aapt2ClientTest { + @field:[Rule JvmField] var tempFolder = TemporaryFolder() + + private val sdkProperties = AndroidBuildSdkProperties() + + private val scriptBgDispatcher by lazy { ScriptBackgroundCoroutineDispatcher() } + private val commandExecutor by lazy { initializeCommandExecutorWithLongProcessWaitTime() } + + // TODO(#4971): Finish the tests for this suite. + + @After + fun tearDown() { + scriptBgDispatcher.close() + } + + @Test + fun testDumpResources_forNonExistentApk_throwsException() { + val aapt2Client = createAapt2Client() + + val exception = assertThrows() { + aapt2Client.dumpResources("fake_file.apk") + } + + assertThat(exception).hasMessageThat().contains("No such file or directory") + } + + private fun createAapt2Client(): Aapt2Client { + return Aapt2Client( + tempFolder.root.absolutePath, + sdkProperties.buildToolsVersion, + scriptBgDispatcher, + commandExecutor + ) + } + + private fun initializeCommandExecutorWithLongProcessWaitTime(): CommandExecutorImpl { + return CommandExecutorImpl( + scriptBgDispatcher, processTimeout = 5, processTimeoutUnit = TimeUnit.MINUTES + ) + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/apkstats/ApkAnalyzerClientTest.kt b/scripts/src/javatests/org/oppia/android/scripts/apkstats/ApkAnalyzerClientTest.kt new file mode 100644 index 00000000000..4df1efba563 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/apkstats/ApkAnalyzerClientTest.kt @@ -0,0 +1,65 @@ +package org.oppia.android.scripts.apkstats + +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.oppia.android.scripts.common.AndroidBuildSdkProperties +import org.oppia.android.scripts.common.CommandExecutorImpl +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.testing.assertThrows +import java.lang.IllegalArgumentException +import java.util.concurrent.TimeUnit + +/** + * Tests for [ApkAnalyzerClient]. + * + * Note that this test executes real commands on the local filesystem. + */ +// Same parameter value: helpers reduce test context, even if they are used by 1 test. +// Function name: test names are conventionally named with underscores. +@Suppress("SameParameterValue", "FunctionName") +class ApkAnalyzerClientTest { + @field:[Rule JvmField] var tempFolder = TemporaryFolder() + + private val sdkProperties = AndroidBuildSdkProperties() + + private val scriptBgDispatcher by lazy { ScriptBackgroundCoroutineDispatcher() } + private val commandExecutor by lazy { initializeCommandExecutorWithLongProcessWaitTime() } + + // TODO(#4971): Finish the tests for this suite. + + @After + fun tearDown() { + scriptBgDispatcher.close() + } + + @Test + fun testComputeDownloadSize_forNonExistentApk_throwsException() { + val apkAnalyzerClient = createApkAnalyzerClient() + + val exception = assertThrows() { + apkAnalyzerClient.computeDownloadSize("fake.apk") + } + + assertThat(exception).hasMessageThat().contains("Cannot open apk") + } + + private fun createApkAnalyzerClient(): ApkAnalyzerClient { + return ApkAnalyzerClient( + Aapt2Client( + tempFolder.root.absolutePath, + sdkProperties.buildToolsVersion, + scriptBgDispatcher, + commandExecutor + ) + ) + } + + private fun initializeCommandExecutorWithLongProcessWaitTime(): CommandExecutorImpl { + return CommandExecutorImpl( + scriptBgDispatcher, processTimeout = 5, processTimeoutUnit = TimeUnit.MINUTES + ) + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/apkstats/BUILD.bazel b/scripts/src/javatests/org/oppia/android/scripts/apkstats/BUILD.bazel new file mode 100644 index 00000000000..78073d09666 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/apkstats/BUILD.bazel @@ -0,0 +1,56 @@ +""" +Tests corresponding to libraries that help with computing APK & AAB stats. +""" + +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test") + +kt_jvm_test( + name = "Aapt2ClientTest", + srcs = ["Aapt2ClientTest.kt"], + data = ["@androidsdk//:aapt2_binary"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/apkstats:aapt2_client", + "//scripts/src/java/org/oppia/android/scripts/common:android_build_sdk_properties", + "//testing:assertion_helpers", + "//third_party:com_google_truth_truth", + ], +) + +kt_jvm_test( + name = "ApkAnalyzerClientTest", + srcs = ["ApkAnalyzerClientTest.kt"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/apkstats:apk_analyzer_client", + "//scripts/src/java/org/oppia/android/scripts/common:android_build_sdk_properties", + "//testing:assertion_helpers", + "//third_party:com_google_truth_truth", + ], +) + +kt_jvm_test( + name = "BundleToolClientTest", + srcs = ["BundleToolClientTest.kt"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/apkstats:bundle_tool_client", + "//testing:assertion_helpers", + "//third_party:com_google_truth_truth", + ], +) + +kt_jvm_test( + name = "ComputeAabDifferencesTest", + srcs = ["ComputeAabDifferencesTest.kt"], + data = ["@androidsdk//:aapt2_binary"], + runtime_deps = [ + "//third_party:android_bundletool", + ], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/apkstats:aapt2_client", + "//scripts/src/java/org/oppia/android/scripts/apkstats:apk_analyzer_client", + "//scripts/src/java/org/oppia/android/scripts/apkstats:bundle_tool_client", + "//scripts/src/java/org/oppia/android/scripts/apkstats:compute_aab_differences_lib", + "//scripts/src/java/org/oppia/android/scripts/common:android_build_sdk_properties", + "//testing:assertion_helpers", + "//third_party:com_google_truth_truth", + ], +) diff --git a/scripts/src/javatests/org/oppia/android/scripts/apkstats/BundleToolClientTest.kt b/scripts/src/javatests/org/oppia/android/scripts/apkstats/BundleToolClientTest.kt new file mode 100644 index 00000000000..3ddf028ff51 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/apkstats/BundleToolClientTest.kt @@ -0,0 +1,51 @@ +package org.oppia.android.scripts.apkstats + +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.oppia.android.scripts.common.CommandExecutorImpl +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.testing.assertThrows +import java.util.concurrent.TimeUnit + +/** + * Tests for [BundleToolClient]. + * + * Note that this test executes real commands on the local filesystem. + */ +// Same parameter value: helpers reduce test context, even if they are used by 1 test. +// Function name: test names are conventionally named with underscores. +@Suppress("SameParameterValue", "FunctionName") +class BundleToolClientTest { + @field:[Rule JvmField] var tempFolder = TemporaryFolder() + + private val scriptBgDispatcher by lazy { ScriptBackgroundCoroutineDispatcher() } + private val commandExecutor by lazy { initializeCommandExecutorWithLongProcessWaitTime() } + + // TODO(#4971): Finish the tests for this suite. + + @After + fun tearDown() { + scriptBgDispatcher.close() + } + + @Test + fun testBuildUniversalApk_forNonExistentAab_throwsException() { + val bundleToolClient = + BundleToolClient(tempFolder.root.absolutePath, scriptBgDispatcher, commandExecutor) + + val exception = assertThrows() { + bundleToolClient.buildUniversalApk("fake.aab", "fake.apk") + } + + assertThat(exception).hasMessageThat().contains("was not found") + } + + private fun initializeCommandExecutorWithLongProcessWaitTime(): CommandExecutorImpl { + return CommandExecutorImpl( + scriptBgDispatcher, processTimeout = 5, processTimeoutUnit = TimeUnit.MINUTES + ) + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/apkstats/ComputeAabDifferencesTest.kt b/scripts/src/javatests/org/oppia/android/scripts/apkstats/ComputeAabDifferencesTest.kt new file mode 100644 index 00000000000..b08753158d7 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/apkstats/ComputeAabDifferencesTest.kt @@ -0,0 +1,69 @@ +package org.oppia.android.scripts.apkstats + +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.oppia.android.scripts.common.AndroidBuildSdkProperties +import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher +import org.oppia.android.testing.assertThrows +import java.lang.IllegalStateException + +/** + * Tests for [ComputeAabDifferences]. + * + * Note that this test executes real commands on the local filesystem. + */ +// Same parameter value: helpers reduce test context, even if they are used by 1 test. +// Function name: test names are conventionally named with underscores. +@Suppress("SameParameterValue", "FunctionName") +class ComputeAabDifferencesTest { + @field:[Rule JvmField] var tempFolder = TemporaryFolder() + + private val scriptBgDispatcher by lazy { ScriptBackgroundCoroutineDispatcher() } + + // TODO(#4971): Finish the tests for this suite. + + @After + fun tearDown() { + scriptBgDispatcher.close() + } + + @Test + fun testComputeBuildStats_forZeroProfiles_returnsEmptyStats() { + val differencesUtility = createComputeAabDifferences() + + val stats = differencesUtility.computeBuildStats() + + assertThat(stats.aabStats).isEmpty() + } + + @Test + fun testComputeBuildStats_forProfileWithMissingFiles_throwsException() { + val differencesUtility = createComputeAabDifferences() + val profile = createProfile(oldAabFilePath = "fake.apk", newAabFilePath = "fake.apk") + + val exception = assertThrows() { + differencesUtility.computeBuildStats(profile) + } + + assertThat(exception).hasMessageThat().contains("was not found") + } + + private fun createComputeAabDifferences(): ComputeAabDifferences { + return ComputeAabDifferences( + workingDirectoryPath = tempFolder.root.absoluteFile.normalize().path, + sdkProperties = AndroidBuildSdkProperties(), + scriptBgDispatcher + ) + } + + private fun createProfile( + oldAabFilePath: String, + newAabFilePath: String, + buildFlavor: String = "dev" + ): ComputeAabDifferences.AabProfile { + return ComputeAabDifferences.AabProfile(buildFlavor, oldAabFilePath, newAabFilePath) + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/common/AndroidBuildSdkPropertiesTest.kt b/scripts/src/javatests/org/oppia/android/scripts/common/AndroidBuildSdkPropertiesTest.kt new file mode 100644 index 00000000000..d7cb9a5ebe9 --- /dev/null +++ b/scripts/src/javatests/org/oppia/android/scripts/common/AndroidBuildSdkPropertiesTest.kt @@ -0,0 +1,31 @@ +package org.oppia.android.scripts.common + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +/** + * Tests for [AndroidBuildSdkProperties]. + * + * Note that these tests have a few caveats: + * 1. They are a bit fragile since they're directly testing the local Bazel configuration. As the + * team changes its dependency versions in the future, this suite will need to be updated. + * 2. There's no way to test the "failing state" since the build graph guarantees that the SDK info + * file is included at runtime. The implementation makes this assumption, as well. + */ +// Function name: test names are conventionally named with underscores. +@Suppress("FunctionName") +class AndroidBuildSdkPropertiesTest { + @Test + fun testBuildSdkVersion_isTheCorrectSdkVersion() { + val properties = AndroidBuildSdkProperties() + + assertThat(properties.buildSdkVersion).isEqualTo(33) + } + + @Test + fun testBuildToolsVersion_isTheCorrectVersion() { + val properties = AndroidBuildSdkProperties() + + assertThat(properties.buildToolsVersion).isEqualTo("29.0.2") + } +} diff --git a/scripts/src/javatests/org/oppia/android/scripts/common/BUILD.bazel b/scripts/src/javatests/org/oppia/android/scripts/common/BUILD.bazel index d021c5d4495..b4e81b569db 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/common/BUILD.bazel +++ b/scripts/src/javatests/org/oppia/android/scripts/common/BUILD.bazel @@ -19,6 +19,15 @@ kt_jvm_test( ], ) +kt_jvm_test( + name = "AndroidBuildSdkPropertiesTest", + srcs = ["AndroidBuildSdkPropertiesTest.kt"], + deps = [ + "//scripts/src/java/org/oppia/android/scripts/common:android_build_sdk_properties", + "//third_party:com_google_truth_truth", + ], +) + kt_jvm_test( name = "CommandExecutorImplTest", srcs = ["CommandExecutorImplTest.kt"], diff --git a/scripts/src/javatests/org/oppia/android/scripts/maven/GenerateMavenDependenciesListTest.kt b/scripts/src/javatests/org/oppia/android/scripts/maven/GenerateMavenDependenciesListTest.kt index 06b290d0e47..ca789a2ada5 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/maven/GenerateMavenDependenciesListTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/maven/GenerateMavenDependenciesListTest.kt @@ -1342,7 +1342,7 @@ class GenerateMavenDependenciesListTest { ) } - /** Helper function to write a fake maven_install.json file. */ + /** Helper function to write a fake Maven install manifest file. */ private fun writeMavenInstallJson(file: File) { file.writeText( """ diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel index f45d5300cd6..74f84964b65 100644 --- a/third_party/BUILD.bazel +++ b/third_party/BUILD.bazel @@ -65,6 +65,14 @@ android_library( ], ) +android_library( + name = "com_google_archivepatcher", + visibility = ["//visibility:public"], + exports = [ + "@archive_patcher//tools", + ], +) + java_library( name = "moshi", exported_plugins = ["//tools:moshi_annotation_processor_plugin"], @@ -132,6 +140,19 @@ kt_jvm_library( exports = [":_kotlinx-coroutines-core-jvm_import_do_not_depend"], ) +java_library( + name = "com_google_guava_guava_jre", + visibility = ["//scripts:__subpackages__"], + exports = [ + "//third_party:com_google_errorprone_error_prone_annotations", + "//third_party:com_google_guava_failureaccess", + "//third_party:com_google_j2objc_j2objc-annotations", + "//third_party:org_checkerframework_checker-compat-qual", + "//third_party:org_checkerframework_checker-qual", + "@guava_jre//jar", + ], +) + # This solution is based on https://github.com/Bencodes/bazel_issue_13553 for Bazel issue: # https://github.com/bazelbuild/bazel/issues/13553. genrule( @@ -162,14 +183,17 @@ java_library( name = "glide_compiler", exported_plugins = ["//tools:glide_compiler_plugin"], visibility = ["//visibility:public"], - exports = [ - "//third_party:com_github_bumptech_glide_compiler", - ], ) -java_binary( +android_library( name = "android_bundletool", + visibility = ["//visibility:public"], + exports = ["@android_bundletool//jar"], +) + +java_binary( + name = "android_bundletool_binary", main_class = "com.android.tools.build.bundletool.BundleToolMain", visibility = ["//visibility:public"], - runtime_deps = ["@android_bundletool//jar"], + runtime_deps = [":android_bundletool"], ) diff --git a/third_party/maven_install.json b/third_party/maven_install.json index 6af4aaf5b4c..f25b55f9094 100644 --- a/third_party/maven_install.json +++ b/third_party/maven_install.json @@ -1,7 +1,7 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -519121942, - "__RESOLVED_ARTIFACTS_HASH": -749919456, + "__INPUT_ARTIFACTS_HASH": 1828687201, + "__RESOLVED_ARTIFACTS_HASH": 1257218600, "conflict_resolution": { "androidx.constraintlayout:constraintlayout:1.1.3": "androidx.constraintlayout:constraintlayout:2.0.1", "androidx.core:core:1.0.1": "androidx.core:core:1.3.1", @@ -514,6 +514,30 @@ }, "version": "3.4.2" }, + "com.android.tools.analytics-library:protos": { + "shasums": { + "jar": "c1f0270c758f61db311d1365af2eaab46318c0ecde1d15aed6c20d5b32ed3ade" + }, + "version": "30.0.4" + }, + "com.android.tools.analytics-library:shared": { + "shasums": { + "jar": "91cada37f5035b5d313cce64fde2a3c3a3f58b267edb80b0e3e94372e3bcf2d1" + }, + "version": "30.0.4" + }, + "com.android.tools.apkparser:apkanalyzer": { + "shasums": { + "jar": "be0f9e079fe166da36127b780de9ffcb297b511b642cc09c0dff75a2ee47c454" + }, + "version": "30.0.4" + }, + "com.android.tools.apkparser:binary-resources": { + "shasums": { + "jar": "c8fb8f656fe329375d4c669fc00bd396e6debd098a1304ab0189e54e6ab3f303" + }, + "version": "30.0.4" + }, "com.android.tools.build.jetifier:jetifier-core": { "shasums": { "jar": "c9f8b016144cfb31c5aee92d47f34de23289167cac5e8ef68365b2dd05766f11" @@ -526,11 +550,113 @@ }, "version": "7.3.1-8691043" }, + "com.android.tools.build:builder-model": { + "shasums": { + "jar": "6ab2f2371f2bcd48a3a623613d0bc00838b5bfb4e6c4e49e324ebcf88f7feb0e" + }, + "version": "7.0.4" + }, + "com.android.tools.build:builder-test-api": { + "shasums": { + "jar": "cfcf933229e368231a96b79b1ec394d7cbcf822eaf8a3ba44c92ba52d959c90e" + }, + "version": "7.0.4" + }, + "com.android.tools.build:manifest-merger": { + "shasums": { + "jar": "44339ed30995742f96147891356135210599c96a0a00a62d81465ce2b8ec1f98" + }, + "version": "30.0.4" + }, + "com.android.tools.ddms:ddmlib": { + "shasums": { + "jar": "7f706a995f5b3047d80e28b4edb584b2338b7b47b9629ed1607011f39c17e14c" + }, + "version": "30.0.4" + }, + "com.android.tools.external.com-intellij:intellij-core": { + "shasums": { + "jar": "e5577bfe5be2937212012cda8ead120fc2dd96a3427533b4b4aed2c60fe3e8c4" + }, + "version": "30.0.4" + }, + "com.android.tools.external.com-intellij:kotlin-compiler": { + "shasums": { + "jar": "5e2456b33e7879442d10c666c2eae756c3e711d087f7b346b1f2035f0c3fc18d" + }, + "version": "30.0.4" + }, + "com.android.tools.external.org-jetbrains:uast": { + "shasums": { + "jar": "72be32fa6463c307ce52abef1317b73bb515e7ff68475ad555cad37b84c1563d" + }, + "version": "30.0.4" + }, + "com.android.tools.layoutlib:layoutlib-api": { + "shasums": { + "jar": "9ba23153dd4aa795313d8b908cd0acd553ec5e2d7b4a5c5944609b6a47a768d4" + }, + "version": "30.0.4" + }, + "com.android.tools.lint:lint-api": { + "shasums": { + "jar": "ebbdcbed4149b8a781262473feaa113166750ca3042735fd9fcd79f8e9103037" + }, + "version": "30.0.4" + }, + "com.android.tools.lint:lint-checks": { + "shasums": { + "jar": "7bd817ff4060fdb13750c5a45daccd768b47a1363607da3ee9eceba46c99d569" + }, + "version": "30.0.4" + }, + "com.android.tools.lint:lint-model": { + "shasums": { + "jar": "fb7561562b7d3d3edf015c12367f267804a9824083a9e10944daf71473ce9bc5" + }, + "version": "30.0.4" + }, "com.android.tools:annotations": { "shasums": { - "jar": "1877849936f48cd65857bf448743f9a0bf93ed47fe57527d9ad8af875024888d" + "jar": "df132b4daa2bb73770cd459c4c6b9b927695dd8a89592ab149ecdb7e0d8506ed" + }, + "version": "30.0.4" + }, + "com.android.tools:common": { + "shasums": { + "jar": "d1aa2b6a43843c385ddd246fb0118acf45afad5317edb3c508a4604a288fa9c9" }, - "version": "26.4.2" + "version": "30.0.4" + }, + "com.android.tools:dvlib": { + "shasums": { + "jar": "61edccccecc1ffca47f442326bd7c1d73f7256ef9adaac834c1d639a142e49b9" + }, + "version": "30.0.4" + }, + "com.android.tools:repository": { + "shasums": { + "jar": "ff77dda7a094a6b2ae5d631ecaca59c26c0673f59b1e5f874352c17e92eb9a1f" + }, + "version": "30.0.4" + }, + "com.android.tools:sdk-common": { + "shasums": { + "jar": "b4617630f9c9cc8ad80f1aab980cc560d8f76bd26e84421ea54f38e036b3be48" + }, + "version": "30.0.4" + }, + "com.android.tools:sdklib": { + "shasums": { + "jar": "bcfe68d2ec02569f40978c9380a3302d60f67772c0dea95838f817f5cf3505a8" + }, + "version": "30.0.4" + }, + "com.beust:jcommander": { + "shasums": { + "jar": "156be736199c990321d9ff77090b199629cfc9865e2d6c13f7cd291bb1641817" + }, + "version": "1.64" }, "com.crashlytics.sdk.android:answers:aar": { "shasums": { @@ -952,6 +1078,12 @@ }, "version": "1.3" }, + "com.google.jimfs:jimfs": { + "shasums": { + "jar": "c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd" + }, + "version": "1.1" + }, "com.google.protobuf:protobuf-java": { "shasums": { "jar": "4ac549b192694141958049f060a1c826a33342f619e108ced8c17d9877f5e3ed" @@ -1072,6 +1204,24 @@ }, "version": "1.10.2" }, + "com.sun.activation:javax.activation": { + "shasums": { + "jar": "993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce" + }, + "version": "1.2.0" + }, + "com.sun.istack:istack-commons-runtime": { + "shasums": { + "jar": "4ffabb06be454a05e4398e20c77fa2b6308d4b88dfbef7ca30a76b5b7d5505ef" + }, + "version": "3.0.8" + }, + "com.sun.xml.fastinfoset:FastInfoset": { + "shasums": { + "jar": "056f3a1e144409f21ed16afc26805f58e9a21f3fce1543c42d400719d250c511" + }, + "version": "1.2.16" + }, "commons-codec:commons-codec": { "shasums": { "jar": "4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569" @@ -1084,6 +1234,12 @@ }, "version": "2.4" }, + "commons-logging:commons-logging": { + "shasums": { + "jar": "daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636" + }, + "version": "1.2" + }, "io.fabric.sdk.android:fabric:aar": { "shasums": { "jar": "36df4b321ec95e31226ff5abaae73e66f3a99dedddbc2d03054c4e141c8aaa5c" @@ -1144,12 +1300,24 @@ }, "version": "0.1.0" }, + "jakarta.activation:jakarta.activation-api": { + "shasums": { + "jar": "8b0a0f52fa8b05c5431921a063ed866efaa41dadf2e3a7ee3e1961f2b0d9645b" + }, + "version": "1.2.1" + }, "jakarta.json:jakarta.json-api": { "shasums": { "jar": "f2472507ad2cc12f2aef08a2f7a628cd1c3f855668a6dcb2aa9a30d9b909b998" }, "version": "2.1.2" }, + "jakarta.xml.bind:jakarta.xml.bind-api": { + "shasums": { + "jar": "69156304079bdeed9fc0ae3b39389f19b3cc4ba4443bc80508995394ead742ea" + }, + "version": "2.3.2" + }, "javax.annotation:javax.annotation-api": { "shasums": { "jar": "e04ba5195bcd555dc95650f7cc614d151e4bcd52d29a10b8aa2197f3ab89ab9b" @@ -1180,6 +1348,18 @@ }, "version": "1.10.20" }, + "net.java.dev.jna:jna": { + "shasums": { + "jar": "5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf" + }, + "version": "5.6.0" + }, + "net.java.dev.jna:jna-platform": { + "shasums": { + "jar": "9ecea8bf2b1b39963939d18b70464eef60c508fed8820f9dcaba0c35518eabf7" + }, + "version": "5.6.0" + }, "net.ltgt.gradle.incap:incap": { "shasums": { "jar": "b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd" @@ -1204,6 +1384,36 @@ }, "version": "4.5.3" }, + "org.apache.commons:commons-compress": { + "shasums": { + "jar": "0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e" + }, + "version": "1.20" + }, + "org.apache.httpcomponents:httpclient": { + "shasums": { + "jar": "c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7" + }, + "version": "4.5.6" + }, + "org.apache.httpcomponents:httpcore": { + "shasums": { + "jar": "78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd" + }, + "version": "4.4.10" + }, + "org.apache.httpcomponents:httpmime": { + "shasums": { + "jar": "0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e" + }, + "version": "4.5.6" + }, + "org.bouncycastle:bcpkix-jdk15on": { + "shasums": { + "jar": "7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca" + }, + "version": "1.56" + }, "org.bouncycastle:bcprov-jdk15on": { "shasums": { "jar": "e78f96eb59066c94c94fb2d6b5eb80f52feac6f5f9776898634f8addec6e2137" @@ -1234,6 +1444,18 @@ }, "version": "1.1.2" }, + "org.glassfish.jaxb:jaxb-runtime": { + "shasums": { + "jar": "e6e0a1e89fb6ff786279e6a0082d5cef52dc2ebe67053d041800737652b4fd1b" + }, + "version": "2.3.2" + }, + "org.glassfish.jaxb:txw2": { + "shasums": { + "jar": "4a6a9f483388d461b81aa9a28c685b8b74c0597993bf1884b04eddbca95f48fe" + }, + "version": "2.3.2" + }, "org.hamcrest:hamcrest-core": { "shasums": { "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9" @@ -1378,6 +1600,12 @@ }, "version": "13.0" }, + "org.jvnet.staxex:stax-ex": { + "shasums": { + "jar": "20522549056e9e50aa35ef0b445a2e47a53d06be0b0a9467d704e2483ffb049a" + }, + "version": "1.8.1" + }, "org.mockito.kotlin:mockito-kotlin": { "shasums": { "jar": "12995c7fdb8de84cf58fca31f1cd8810eeaa39e63c00e61ffcb2b49a4305f21f" @@ -1492,11 +1720,41 @@ }, "version": "4.5" }, + "org.smali:baksmali": { + "shasums": { + "jar": "1990932c4c8cf334a640e52c4bd0426e4abb2c2fc94f1803711ea8460c06de50" + }, + "version": "2.2.4" + }, + "org.smali:dexlib2": { + "shasums": { + "jar": "cb2677bfb66cfbc954e96e806ac6bda13051ad37754f9d1bcce38514e50e41e6" + }, + "version": "2.2.4" + }, + "org.smali:util": { + "shasums": { + "jar": "d7de146d2b6dc9d0b75cb6c7ff17ca68ef24049dc6038b84b5f6b22cd00ddd46" + }, + "version": "2.2.4" + }, "org.snakeyaml:snakeyaml-engine": { "shasums": { "jar": "2652199af40c9aa2f1782400d2dfbbf4e5226208c4e05ddd4059c3d6d9cd1505" }, "version": "2.6" + }, + "xerces:xercesImpl": { + "shasums": { + "jar": "b50d3a4ca502faa4d1c838acb8aa9480446953421f7327e338c5dda3da5e76d0" + }, + "version": "2.12.0" + }, + "xml-apis:xml-apis": { + "shasums": { + "jar": "a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad" + }, + "version": "1.4.01" } }, "dependencies": { @@ -1884,6 +2142,34 @@ "androidx.room:room-runtime:aar", "androidx.work:work-runtime-ktx:aar" ], + "com.android.tools.analytics-library:protos": [ + "com.google.protobuf:protobuf-java" + ], + "com.android.tools.analytics-library:shared": [ + "com.android.tools.analytics-library:protos", + "com.android.tools:annotations", + "com.android.tools:common", + "com.google.code.gson:gson", + "com.google.guava:guava", + "net.java.dev.jna:jna-platform", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + ], + "com.android.tools.apkparser:apkanalyzer": [ + "com.android.tools.apkparser:binary-resources", + "com.android.tools.lint:lint-api", + "com.android.tools.lint:lint-checks", + "com.android.tools:annotations", + "com.android.tools:common", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "org.smali:baksmali", + "org.smali:dexlib2" + ], + "com.android.tools.apkparser:binary-resources": [ + "com.android.tools:annotations", + "com.google.code.findbugs:jsr305", + "com.google.guava:guava" + ], "com.android.tools.build.jetifier:jetifier-core": [ "com.google.code.gson:gson", "org.jetbrains.kotlin:kotlin-stdlib" @@ -1891,6 +2177,130 @@ "com.android.tools.build:aapt2-proto": [ "com.google.protobuf:protobuf-java" ], + "com.android.tools.build:builder-model": [ + "com.android.tools:annotations", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + ], + "com.android.tools.build:builder-test-api": [ + "com.android.tools.ddms:ddmlib", + "com.android.tools:annotations", + "com.android.tools:common", + "com.google.guava:guava" + ], + "com.android.tools.build:manifest-merger": [ + "com.android.tools:common", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.google.code.gson:gson", + "net.sf.kxml:kxml2", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + ], + "com.android.tools.ddms:ddmlib": [ + "com.android.tools:common", + "com.google.protobuf:protobuf-java", + "net.sf.kxml:kxml2" + ], + "com.android.tools.external.com-intellij:intellij-core": [ + "org.jetbrains.intellij.deps:trove4j" + ], + "com.android.tools.layoutlib:layoutlib-api": [ + "com.android.tools:annotations", + "com.android.tools:common", + "net.sf.kxml:kxml2", + "org.jetbrains:annotations" + ], + "com.android.tools.lint:lint-api": [ + "com.android.tools.build:builder-model", + "com.android.tools.build:manifest-merger", + "com.android.tools.external.com-intellij:intellij-core", + "com.android.tools.external.com-intellij:kotlin-compiler", + "com.android.tools.external.org-jetbrains:uast", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools.lint:lint-model", + "com.android.tools:common", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.google.guava:guava", + "commons-io:commons-io", + "net.sf.kxml:kxml2", + "org.jetbrains.kotlin:kotlin-reflect", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + "org.ow2.asm:asm", + "org.ow2.asm:asm-tree" + ], + "com.android.tools.lint:lint-checks": [ + "com.android.tools.external.com-intellij:intellij-core", + "com.android.tools.external.com-intellij:kotlin-compiler", + "com.android.tools.external.org-jetbrains:uast", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools.lint:lint-api", + "com.android.tools:common", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.google.code.gson:gson", + "com.google.guava:guava", + "net.sf.kxml:kxml2", + "org.jetbrains.intellij.deps:trove4j", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + "org.ow2.asm:asm-analysis" + ], + "com.android.tools.lint:lint-model": [ + "com.android.tools.build:builder-model", + "com.android.tools:common", + "com.android.tools:sdk-common", + "net.sf.kxml:kxml2", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + ], + "com.android.tools:common": [ + "com.android.tools:annotations", + "com.google.guava:guava", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + ], + "com.android.tools:dvlib": [ + "com.android.tools:common" + ], + "com.android.tools:repository": [ + "com.android.tools.analytics-library:shared", + "com.android.tools:common", + "com.google.jimfs:jimfs", + "com.sun.activation:javax.activation", + "org.apache.commons:commons-compress", + "org.glassfish.jaxb:jaxb-runtime", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + ], + "com.android.tools:sdk-common": [ + "com.android.tools.analytics-library:shared", + "com.android.tools.build:aapt2-proto", + "com.android.tools.build:builder-model", + "com.android.tools.build:builder-test-api", + "com.android.tools.ddms:ddmlib", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools:common", + "com.android.tools:sdklib", + "com.google.code.gson:gson", + "com.google.guava:guava", + "com.google.protobuf:protobuf-java", + "javax.inject:javax.inject", + "net.sf.kxml:kxml2", + "org.bouncycastle:bcpkix-jdk15on", + "org.bouncycastle:bcprov-jdk15on", + "org.glassfish.jaxb:jaxb-runtime", + "org.jetbrains.intellij.deps:trove4j", + "org.jetbrains.kotlin:kotlin-reflect", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", + "xerces:xercesImpl" + ], + "com.android.tools:sdklib": [ + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools:common", + "com.android.tools:dvlib", + "com.android.tools:repository", + "com.google.code.gson:gson", + "org.apache.commons:commons-compress", + "org.apache.httpcomponents:httpcore", + "org.apache.httpcomponents:httpmime", + "org.glassfish.jaxb:jaxb-runtime" + ], "com.crashlytics.sdk.android:answers:aar": [ "io.fabric.sdk.android:fabric:aar" ], @@ -2237,6 +2647,9 @@ "com.google.j2objc:j2objc-annotations", "org.checkerframework:checker-qual" ], + "com.google.jimfs:jimfs": [ + "com.google.guava:guava" + ], "com.google.protobuf:protobuf-java-util": [ "com.google.code.gson:gson", "com.google.errorprone:error_prone_annotations", @@ -2305,6 +2718,9 @@ "org.jetbrains.kotlin:kotlin-reflect", "org.jetbrains.kotlin:kotlin-stdlib-jdk8" ], + "com.sun.istack:istack-commons-runtime": [ + "jakarta.activation:jakarta.activation-api" + ], "io.grpc:grpc-android:aar": [ "com.google.guava:guava", "io.grpc:grpc-core" @@ -2342,15 +2758,40 @@ "com.google.guava:guava", "io.grpc:grpc-api" ], + "jakarta.xml.bind:jakarta.xml.bind-api": [ + "jakarta.activation:jakarta.activation-api" + ], "junit:junit": [ "org.hamcrest:hamcrest-core" ], + "net.java.dev.jna:jna-platform": [ + "net.java.dev.jna:jna" + ], "nl.dionsegijn:konfetti:aar": [ "org.jetbrains.kotlin:kotlin-stdlib" ], + "org.apache.httpcomponents:httpclient": [ + "commons-codec:commons-codec", + "commons-logging:commons-logging", + "org.apache.httpcomponents:httpcore" + ], + "org.apache.httpcomponents:httpmime": [ + "org.apache.httpcomponents:httpclient" + ], + "org.bouncycastle:bcpkix-jdk15on": [ + "org.bouncycastle:bcprov-jdk15on" + ], "org.eclipse.parsson:parsson": [ "jakarta.json:jakarta.json-api" ], + "org.glassfish.jaxb:jaxb-runtime": [ + "com.sun.istack:istack-commons-runtime", + "com.sun.xml.fastinfoset:FastInfoset", + "jakarta.activation:jakarta.activation-api", + "jakarta.xml.bind:jakarta.xml.bind-api", + "org.glassfish.jaxb:txw2", + "org.jvnet.staxex:stax-ex" + ], "org.hamcrest:hamcrest-integration": [ "org.hamcrest:hamcrest-library" ], @@ -2420,6 +2861,10 @@ "org.jetbrains.kotlinx:kotlinx-metadata-jvm": [ "org.jetbrains.kotlin:kotlin-stdlib" ], + "org.jvnet.staxex:stax-ex": [ + "jakarta.activation:jakarta.activation-api", + "jakarta.xml.bind:jakarta.xml.bind-api" + ], "org.mockito.kotlin:mockito-kotlin": [ "org.mockito:mockito-core" ], @@ -2519,6 +2964,25 @@ "org.ow2.asm:asm-commons", "org.ow2.asm:asm-util", "org.robolectric:utils" + ], + "org.smali:baksmali": [ + "com.beust:jcommander", + "com.google.guava:guava", + "org.smali:dexlib2", + "org.smali:util" + ], + "org.smali:dexlib2": [ + "com.google.code.findbugs:jsr305", + "com.google.guava:guava", + "org.smali:util" + ], + "org.smali:util": [ + "com.beust:jcommander", + "com.google.code.findbugs:jsr305", + "com.google.guava:guava" + ], + "xerces:xercesImpl": [ + "xml-apis:xml-apis" ] }, "packages": { @@ -2577,6 +3041,24 @@ "com.android.databinding:baseLibrary": [ "android.databinding" ], + "com.android.tools.analytics-library:protos": [ + "com.android.tools.build.gradle.internal.profile", + "com.google.wireless.android.play.playlog.proto", + "com.google.wireless.android.sdk.stats" + ], + "com.android.tools.analytics-library:shared": [ + "com.android.tools.analytics" + ], + "com.android.tools.apkparser:apkanalyzer": [ + "com.android.tools.apk.analyzer", + "com.android.tools.apk.analyzer.dex", + "com.android.tools.apk.analyzer.dex.tree", + "com.android.tools.apk.analyzer.internal", + "com.android.tools.apk.analyzer.internal.rewriters" + ], + "com.android.tools.apkparser:binary-resources": [ + "com.google.devrel.gmscore.tools.apk.arsc" + ], "com.android.tools.build.jetifier:jetifier-core": [ "com.android.tools.build.jetifier.core", "com.android.tools.build.jetifier.core.config", @@ -2590,38 +3072,1165 @@ "android.aapt.pb.internal", "com.android.aapt" ], - "com.android.tools:annotations": [ - "com.android.annotations", - "com.android.annotations.concurrency" - ], - "com.github.bumptech.glide:annotations": [ - "com.bumptech.glide.annotation", - "com.bumptech.glide.annotation.compiler" - ], - "com.github.bumptech.glide:compiler": [ - "com.bumptech.glide.annotation.compiler", - "com.bumptech.glide.repackaged.com.google.common.base", - "com.bumptech.glide.repackaged.com.google.common.collect", - "com.bumptech.glide.repackaged.com.squareup.javapoet" - ], - "com.github.bumptech.glide:disklrucache": [ - "com.bumptech.glide.disklrucache" - ], - "com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework": [ - "com.google.android.apps.common.testing.accessibility.framework", - "com.google.android.apps.common.testing.accessibility.framework.integrations", - "com.google.android.apps.common.testing.accessibility.framework.integrations.espresso", - "com.googlecode.eyesfree.compat", - "com.googlecode.eyesfree.utils" - ], - "com.google.android.gms:strict-version-matcher-plugin": [ - "com.google.android.gms", - "com.google.android.gms.dependencies" - ], - "com.google.android:annotations": [ - "android.annotation" - ], - "com.google.auto.service:auto-service-annotations": [ + "com.android.tools.build:builder-model": [ + "com.android.build", + "com.android.builder.model", + "com.android.builder.model.level2", + "com.android.builder.model.v2", + "com.android.builder.model.v2.dsl", + "com.android.builder.model.v2.ide", + "com.android.builder.model.v2.models", + "com.android.builder.model.v2.models.ndk" + ], + "com.android.tools.build:builder-test-api": [ + "com.android.builder.testing.api" + ], + "com.android.tools.build:manifest-merger": [ + "com.android.manifmerger" + ], + "com.android.tools.ddms:ddmlib": [ + "com.android.commands.am", + "com.android.ddmlib", + "com.android.ddmlib.internal", + "com.android.ddmlib.internal.jdwp", + "com.android.ddmlib.internal.jdwp.chunkhandler", + "com.android.ddmlib.internal.jdwp.interceptor", + "com.android.ddmlib.jdwp", + "com.android.ddmlib.jdwp.packets", + "com.android.ddmlib.log", + "com.android.ddmlib.logcat", + "com.android.ddmlib.testrunner", + "com.android.ddmlib.utils", + "com.android.incfs.install", + "com.android.incfs.install.adb.ddmlib", + "com.android.server.adb.protos" + ], + "com.android.tools.external.com-intellij:intellij-core": [ + "com.android.tools.external.intellij.core", + "com.intellij", + "com.intellij.codeInsight", + "com.intellij.codeInsight.completion", + "com.intellij.codeInsight.completion.proc", + "com.intellij.codeInsight.completion.scope", + "com.intellij.codeInsight.controlflow", + "com.intellij.codeInsight.controlflow.impl", + "com.intellij.codeInsight.daemon.impl", + "com.intellij.codeInsight.daemon.impl.analysis", + "com.intellij.codeInsight.documentation", + "com.intellij.codeInsight.folding", + "com.intellij.codeInsight.folding.impl", + "com.intellij.codeInsight.generation", + "com.intellij.codeInsight.highlighting", + "com.intellij.codeInsight.javadoc", + "com.intellij.codeInsight.runner", + "com.intellij.codeInspection", + "com.intellij.codeWithMe", + "com.intellij.concurrency", + "com.intellij.core", + "com.intellij.diagnostic", + "com.intellij.diagnostic.tracing", + "com.intellij.execution", + "com.intellij.execution.configurations", + "com.intellij.execution.process", + "com.intellij.execution.rmi", + "com.intellij.execution.rmi.ssl", + "com.intellij.extapi.psi", + "com.intellij.formatting", + "com.intellij.icons", + "com.intellij.ide", + "com.intellij.ide.fileTemplates", + "com.intellij.ide.highlighter", + "com.intellij.ide.highlighter.custom", + "com.intellij.ide.highlighter.custom.tokens", + "com.intellij.ide.lightEdit", + "com.intellij.ide.plugins", + "com.intellij.ide.plugins.cl", + "com.intellij.ide.util", + "com.intellij.indentation", + "com.intellij.injected.editor", + "com.intellij.jna", + "com.intellij.lang", + "com.intellij.lang.folding", + "com.intellij.lang.impl", + "com.intellij.lang.injection", + "com.intellij.lang.java", + "com.intellij.lang.java.beans", + "com.intellij.lang.java.lexer", + "com.intellij.lang.java.parser", + "com.intellij.lang.java.source", + "com.intellij.lang.jvm", + "com.intellij.lang.jvm.annotation", + "com.intellij.lang.jvm.facade", + "com.intellij.lang.jvm.source", + "com.intellij.lang.jvm.types", + "com.intellij.lang.jvm.util", + "com.intellij.lang.spi", + "com.intellij.lexer", + "com.intellij.mock", + "com.intellij.model", + "com.intellij.model.presentation", + "com.intellij.model.psi", + "com.intellij.model.psi.impl", + "com.intellij.model.search", + "com.intellij.navigation", + "com.intellij.notebook.editor", + "com.intellij.openapi", + "com.intellij.openapi.application", + "com.intellij.openapi.application.ex", + "com.intellij.openapi.application.impl", + "com.intellij.openapi.command", + "com.intellij.openapi.command.impl", + "com.intellij.openapi.command.undo", + "com.intellij.openapi.components", + "com.intellij.openapi.diagnostic", + "com.intellij.openapi.diff", + "com.intellij.openapi.diff.ex", + "com.intellij.openapi.diff.impl", + "com.intellij.openapi.diff.impl.fragments", + "com.intellij.openapi.diff.impl.highlighting", + "com.intellij.openapi.diff.impl.patch", + "com.intellij.openapi.diff.impl.processing", + "com.intellij.openapi.diff.impl.string", + "com.intellij.openapi.diff.impl.util", + "com.intellij.openapi.editor", + "com.intellij.openapi.editor.actionSystem", + "com.intellij.openapi.editor.colors", + "com.intellij.openapi.editor.event", + "com.intellij.openapi.editor.ex", + "com.intellij.openapi.editor.ex.util", + "com.intellij.openapi.editor.impl", + "com.intellij.openapi.editor.impl.event", + "com.intellij.openapi.editor.markup", + "com.intellij.openapi.extensions", + "com.intellij.openapi.extensions.impl", + "com.intellij.openapi.fileEditor", + "com.intellij.openapi.fileEditor.impl", + "com.intellij.openapi.fileTypes", + "com.intellij.openapi.fileTypes.ex", + "com.intellij.openapi.fileTypes.impl", + "com.intellij.openapi.module", + "com.intellij.openapi.module.impl", + "com.intellij.openapi.options", + "com.intellij.openapi.progress", + "com.intellij.openapi.progress.impl", + "com.intellij.openapi.progress.util", + "com.intellij.openapi.project", + "com.intellij.openapi.projectRoots", + "com.intellij.openapi.roots", + "com.intellij.openapi.roots.impl", + "com.intellij.openapi.startup", + "com.intellij.openapi.ui", + "com.intellij.openapi.util", + "com.intellij.openapi.util.io", + "com.intellij.openapi.util.io.win32", + "com.intellij.openapi.util.objectTree", + "com.intellij.openapi.util.registry", + "com.intellij.openapi.util.text", + "com.intellij.openapi.vfs", + "com.intellij.openapi.vfs.encoding", + "com.intellij.openapi.vfs.ex", + "com.intellij.openapi.vfs.ex.http", + "com.intellij.openapi.vfs.impl", + "com.intellij.openapi.vfs.impl.http", + "com.intellij.openapi.vfs.impl.jar", + "com.intellij.openapi.vfs.local", + "com.intellij.openapi.vfs.newvfs", + "com.intellij.openapi.vfs.newvfs.events", + "com.intellij.openapi.vfs.pointers", + "com.intellij.openapi.wm.ex", + "com.intellij.patterns", + "com.intellij.patterns.compiler", + "com.intellij.pom", + "com.intellij.pom.core.impl", + "com.intellij.pom.event", + "com.intellij.pom.impl", + "com.intellij.pom.java", + "com.intellij.pom.references", + "com.intellij.pom.tree", + "com.intellij.pom.tree.events", + "com.intellij.pom.tree.events.impl", + "com.intellij.pom.wrappers", + "com.intellij.psi", + "com.intellij.psi.augment", + "com.intellij.psi.codeStyle", + "com.intellij.psi.compiled", + "com.intellij.psi.controlFlow", + "com.intellij.psi.css", + "com.intellij.psi.filters", + "com.intellij.psi.filters.classes", + "com.intellij.psi.filters.element", + "com.intellij.psi.filters.position", + "com.intellij.psi.impl", + "com.intellij.psi.impl.cache", + "com.intellij.psi.impl.compiled", + "com.intellij.psi.impl.file", + "com.intellij.psi.impl.file.impl", + "com.intellij.psi.impl.java.stubs", + "com.intellij.psi.impl.java.stubs.impl", + "com.intellij.psi.impl.java.stubs.index", + "com.intellij.psi.impl.light", + "com.intellij.psi.impl.meta", + "com.intellij.psi.impl.search", + "com.intellij.psi.impl.smartPointers", + "com.intellij.psi.impl.source", + "com.intellij.psi.impl.source.codeStyle", + "com.intellij.psi.impl.source.javadoc", + "com.intellij.psi.impl.source.resolve", + "com.intellij.psi.impl.source.resolve.graphInference", + "com.intellij.psi.impl.source.resolve.graphInference.constraints", + "com.intellij.psi.impl.source.resolve.reference", + "com.intellij.psi.impl.source.resolve.reference.impl", + "com.intellij.psi.impl.source.resolve.reference.impl.manipulators", + "com.intellij.psi.impl.source.resolve.reference.impl.providers", + "com.intellij.psi.impl.source.tree", + "com.intellij.psi.impl.source.tree.injected", + "com.intellij.psi.impl.source.tree.java", + "com.intellij.psi.infos", + "com.intellij.psi.javadoc", + "com.intellij.psi.meta", + "com.intellij.psi.presentation.java", + "com.intellij.psi.scope", + "com.intellij.psi.scope.conflictResolvers", + "com.intellij.psi.scope.processor", + "com.intellij.psi.scope.util", + "com.intellij.psi.search", + "com.intellij.psi.search.searches", + "com.intellij.psi.stub", + "com.intellij.psi.stubs", + "com.intellij.psi.targets", + "com.intellij.psi.templateLanguages", + "com.intellij.psi.text", + "com.intellij.psi.tree", + "com.intellij.psi.tree.java", + "com.intellij.psi.util", + "com.intellij.refactoring.rename", + "com.intellij.refactoring.util", + "com.intellij.reference", + "com.intellij.security", + "com.intellij.serialization", + "com.intellij.serviceContainer", + "com.intellij.testFramework", + "com.intellij.testIntegration", + "com.intellij.ui", + "com.intellij.ui.icons", + "com.intellij.usageView", + "com.intellij.util", + "com.intellij.util.cls", + "com.intellij.util.codeInsight", + "com.intellij.util.concurrency", + "com.intellij.util.concurrency.annotations", + "com.intellij.util.containers", + "com.intellij.util.containers.hash", + "com.intellij.util.diff", + "com.intellij.util.enumeration", + "com.intellij.util.exception", + "com.intellij.util.execution", + "com.intellij.util.graph", + "com.intellij.util.graph.impl", + "com.intellij.util.indexing", + "com.intellij.util.indexing.containers", + "com.intellij.util.indexing.flavor", + "com.intellij.util.indexing.impl", + "com.intellij.util.indexing.impl.forward", + "com.intellij.util.io", + "com.intellij.util.io.externalizer", + "com.intellij.util.io.keyStorage", + "com.intellij.util.io.storage", + "com.intellij.util.io.zip", + "com.intellij.util.keyFMap", + "com.intellij.util.lang", + "com.intellij.util.lang.fastutil", + "com.intellij.util.loader", + "com.intellij.util.messages", + "com.intellij.util.messages.impl", + "com.intellij.util.pico", + "com.intellij.util.profiling", + "com.intellij.util.properties", + "com.intellij.util.ref", + "com.intellij.util.system", + "com.intellij.util.text", + "com.intellij.util.ui", + "com.intellij.util.xmlb", + "com.intellij.util.xmlb.annotations", + "com.intellij.xml", + "com.intellij.xml.util", + "com.sun.jna", + "com.sun.jna.internal", + "com.sun.jna.platform", + "com.sun.jna.platform.dnd", + "com.sun.jna.platform.linux", + "com.sun.jna.platform.mac", + "com.sun.jna.platform.unix", + "com.sun.jna.platform.unix.aix", + "com.sun.jna.platform.unix.solaris", + "com.sun.jna.platform.win32", + "com.sun.jna.platform.win32.COM", + "com.sun.jna.platform.win32.COM.tlb", + "com.sun.jna.platform.win32.COM.tlb.imp", + "com.sun.jna.platform.win32.COM.util", + "com.sun.jna.platform.win32.COM.util.annotation", + "com.sun.jna.platform.wince", + "com.sun.jna.ptr", + "com.sun.jna.win32", + "it.unimi.dsi.fastutil", + "it.unimi.dsi.fastutil.booleans", + "it.unimi.dsi.fastutil.bytes", + "it.unimi.dsi.fastutil.chars", + "it.unimi.dsi.fastutil.doubles", + "it.unimi.dsi.fastutil.floats", + "it.unimi.dsi.fastutil.ints", + "it.unimi.dsi.fastutil.longs", + "it.unimi.dsi.fastutil.objects", + "it.unimi.dsi.fastutil.shorts", + "javaslang", + "javaslang.collection", + "javaslang.concurrent", + "javaslang.control", + "javaslang.match", + "javaslang.match.annotation", + "javaslang.match.generator", + "javaslang.match.model", + "javax.annotation", + "javax.annotation.concurrent", + "javax.annotation.meta", + "javax.inject", + "net.jpountz.lz4", + "net.jpountz.util", + "net.jpountz.xxhash", + "one.util.streamex", + "org.apache.log4j", + "org.apache.log4j.chainsaw", + "org.apache.log4j.config", + "org.apache.log4j.helpers", + "org.apache.log4j.jdbc", + "org.apache.log4j.jmx", + "org.apache.log4j.lf5", + "org.apache.log4j.lf5.util", + "org.apache.log4j.lf5.viewer", + "org.apache.log4j.lf5.viewer.categoryexplorer", + "org.apache.log4j.lf5.viewer.configure", + "org.apache.log4j.net", + "org.apache.log4j.nt", + "org.apache.log4j.or", + "org.apache.log4j.or.jms", + "org.apache.log4j.or.sax", + "org.apache.log4j.pattern", + "org.apache.log4j.rewrite", + "org.apache.log4j.spi", + "org.apache.log4j.varia", + "org.apache.log4j.xml", + "org.apache.oro.io", + "org.apache.oro.text", + "org.apache.oro.text.awk", + "org.apache.oro.text.perl", + "org.apache.oro.text.regex", + "org.apache.oro.util", + "org.fusesource.hawtjni.runtime", + "org.fusesource.jansi", + "org.fusesource.jansi.internal", + "org.intellij.lang.annotations", + "org.jdom", + "org.jdom.adapters", + "org.jdom.filter", + "org.jdom.filter2", + "org.jdom.input", + "org.jdom.input.sax", + "org.jdom.input.stax", + "org.jdom.internal", + "org.jdom.located", + "org.jdom.output", + "org.jdom.output.support", + "org.jdom.transform", + "org.jdom.util", + "org.jdom.xpath", + "org.jdom.xpath.jaxen", + "org.jdom.xpath.util", + "org.jetbrains.annotations", + "org.jetbrains.concurrency", + "org.jetbrains.ide", + "org.jetbrains.jps", + "org.jetbrains.jps.plugin", + "org.jetbrains.jps.service", + "org.jetbrains.jps.service.impl", + "org.jetbrains.jps.util", + "org.jetbrains.org.objectweb.asm", + "org.jetbrains.org.objectweb.asm.commons", + "org.jetbrains.org.objectweb.asm.signature", + "org.jetbrains.org.objectweb.asm.tree", + "org.jetbrains.org.objectweb.asm.tree.analysis", + "org.jetbrains.org.objectweb.asm.util", + "org.jline.builtins", + "org.jline.builtins.ssh", + "org.jline.builtins.telnet", + "org.jline.keymap", + "org.jline.reader", + "org.jline.reader.impl", + "org.jline.reader.impl.completer", + "org.jline.reader.impl.history", + "org.jline.terminal", + "org.jline.terminal.impl", + "org.jline.terminal.impl.jansi", + "org.jline.terminal.impl.jansi.freebsd", + "org.jline.terminal.impl.jansi.linux", + "org.jline.terminal.impl.jansi.osx", + "org.jline.terminal.impl.jansi.solaris", + "org.jline.terminal.impl.jansi.win", + "org.jline.terminal.impl.jna", + "org.jline.terminal.impl.jna.freebsd", + "org.jline.terminal.impl.jna.linux", + "org.jline.terminal.impl.jna.osx", + "org.jline.terminal.impl.jna.solaris", + "org.jline.terminal.impl.jna.win", + "org.jline.terminal.spi", + "org.jline.utils", + "org.picocontainer", + "org.picocontainer.defaults" + ], + "com.android.tools.external.com-intellij:kotlin-compiler": [ + "com.android.tools.external.kotlin.compiler", + "kotlin.script.dependencies", + "kotlin.script.experimental.annotations", + "kotlin.script.experimental.api", + "kotlin.script.experimental.dependencies", + "kotlin.script.experimental.host", + "kotlin.script.experimental.jvm", + "kotlin.script.experimental.jvm.compat", + "kotlin.script.experimental.jvm.impl", + "kotlin.script.experimental.jvm.util", + "kotlin.script.experimental.location", + "kotlin.script.experimental.util", + "kotlin.script.extensions", + "kotlin.script.templates", + "kotlin.script.templates.standard", + "kotlinx.collections.immutable", + "kotlinx.collections.immutable.adapters", + "kotlinx.collections.immutable.implementations.immutableList", + "kotlinx.collections.immutable.implementations.immutableMap", + "kotlinx.collections.immutable.implementations.immutableSet", + "kotlinx.collections.immutable.implementations.persistentOrderedMap", + "kotlinx.collections.immutable.implementations.persistentOrderedSet", + "kotlinx.collections.immutable.internal", + "org.jetbrains.jps.model", + "org.jetbrains.jps.model.artifact", + "org.jetbrains.jps.model.artifact.elements", + "org.jetbrains.jps.model.artifact.elements.ex", + "org.jetbrains.jps.model.artifact.impl", + "org.jetbrains.jps.model.artifact.impl.elements", + "org.jetbrains.jps.model.ex", + "org.jetbrains.jps.model.fileTypes", + "org.jetbrains.jps.model.fileTypes.impl", + "org.jetbrains.jps.model.impl", + "org.jetbrains.jps.model.impl.runConfiguration", + "org.jetbrains.jps.model.jarRepository", + "org.jetbrains.jps.model.jarRepository.impl", + "org.jetbrains.jps.model.java", + "org.jetbrains.jps.model.java.compiler", + "org.jetbrains.jps.model.java.impl", + "org.jetbrains.jps.model.java.impl.compiler", + "org.jetbrains.jps.model.java.impl.runConfiguration", + "org.jetbrains.jps.model.java.runConfiguration", + "org.jetbrains.jps.model.library", + "org.jetbrains.jps.model.library.impl", + "org.jetbrains.jps.model.library.impl.sdk", + "org.jetbrains.jps.model.library.sdk", + "org.jetbrains.jps.model.module", + "org.jetbrains.jps.model.module.impl", + "org.jetbrains.jps.model.runConfiguration", + "org.jetbrains.jps.model.serialization", + "org.jetbrains.jps.model.serialization.artifact", + "org.jetbrains.jps.model.serialization.facet", + "org.jetbrains.jps.model.serialization.impl", + "org.jetbrains.jps.model.serialization.jarRepository", + "org.jetbrains.jps.model.serialization.java", + "org.jetbrains.jps.model.serialization.java.compiler", + "org.jetbrains.jps.model.serialization.library", + "org.jetbrains.jps.model.serialization.module", + "org.jetbrains.jps.model.serialization.runConfigurations", + "org.jetbrains.kotlin", + "org.jetbrains.kotlin.analyzer", + "org.jetbrains.kotlin.analyzer.common", + "org.jetbrains.kotlin.asJava", + "org.jetbrains.kotlin.asJava.builder", + "org.jetbrains.kotlin.asJava.classes", + "org.jetbrains.kotlin.asJava.elements", + "org.jetbrains.kotlin.asJava.finder", + "org.jetbrains.kotlin.backend.common", + "org.jetbrains.kotlin.backend.common.bridges", + "org.jetbrains.kotlin.backend.common.descriptors", + "org.jetbrains.kotlin.backend.common.extensions", + "org.jetbrains.kotlin.backend.common.ir", + "org.jetbrains.kotlin.backend.common.lower", + "org.jetbrains.kotlin.backend.common.lower.inline", + "org.jetbrains.kotlin.backend.common.lower.loops", + "org.jetbrains.kotlin.backend.common.lower.loops.handlers", + "org.jetbrains.kotlin.backend.common.lower.matchers", + "org.jetbrains.kotlin.backend.common.lower.optimizations", + "org.jetbrains.kotlin.backend.common.output", + "org.jetbrains.kotlin.backend.common.overrides", + "org.jetbrains.kotlin.backend.common.phaser", + "org.jetbrains.kotlin.backend.common.serialization", + "org.jetbrains.kotlin.backend.common.serialization.encodings", + "org.jetbrains.kotlin.backend.common.serialization.mangle", + "org.jetbrains.kotlin.backend.common.serialization.mangle.descriptor", + "org.jetbrains.kotlin.backend.common.serialization.mangle.ir", + "org.jetbrains.kotlin.backend.common.serialization.metadata", + "org.jetbrains.kotlin.backend.common.serialization.metadata.impl", + "org.jetbrains.kotlin.backend.common.serialization.proto", + "org.jetbrains.kotlin.backend.common.serialization.signature", + "org.jetbrains.kotlin.backend.jvm", + "org.jetbrains.kotlin.backend.jvm.codegen", + "org.jetbrains.kotlin.backend.jvm.descriptors", + "org.jetbrains.kotlin.backend.jvm.intrinsics", + "org.jetbrains.kotlin.backend.jvm.ir", + "org.jetbrains.kotlin.backend.jvm.lower", + "org.jetbrains.kotlin.backend.jvm.lower.indy", + "org.jetbrains.kotlin.backend.jvm.lower.inlineclasses", + "org.jetbrains.kotlin.backend.jvm.serialization", + "org.jetbrains.kotlin.backend.wasm", + "org.jetbrains.kotlin.backend.wasm.ir2wasm", + "org.jetbrains.kotlin.backend.wasm.lower", + "org.jetbrains.kotlin.backend.wasm.utils", + "org.jetbrains.kotlin.build", + "org.jetbrains.kotlin.build.report", + "org.jetbrains.kotlin.build.report.metrics", + "org.jetbrains.kotlin.builtins", + "org.jetbrains.kotlin.builtins.functions", + "org.jetbrains.kotlin.builtins.jvm", + "org.jetbrains.kotlin.builtins.konan", + "org.jetbrains.kotlin.cfg", + "org.jetbrains.kotlin.cfg.pseudocode", + "org.jetbrains.kotlin.cfg.pseudocode.instructions", + "org.jetbrains.kotlin.cfg.pseudocode.instructions.eval", + "org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps", + "org.jetbrains.kotlin.cfg.pseudocode.instructions.special", + "org.jetbrains.kotlin.cfg.pseudocodeTraverser", + "org.jetbrains.kotlin.cfg.variable", + "org.jetbrains.kotlin.checkers", + "org.jetbrains.kotlin.checkers.diagnostics", + "org.jetbrains.kotlin.checkers.diagnostics.factories", + "org.jetbrains.kotlin.checkers.utils", + "org.jetbrains.kotlin.cli.common", + "org.jetbrains.kotlin.cli.common.arguments", + "org.jetbrains.kotlin.cli.common.config", + "org.jetbrains.kotlin.cli.common.environment", + "org.jetbrains.kotlin.cli.common.extensions", + "org.jetbrains.kotlin.cli.common.messages", + "org.jetbrains.kotlin.cli.common.modules", + "org.jetbrains.kotlin.cli.common.output", + "org.jetbrains.kotlin.cli.common.profiling", + "org.jetbrains.kotlin.cli.common.repl", + "org.jetbrains.kotlin.cli.js", + "org.jetbrains.kotlin.cli.js.dce", + "org.jetbrains.kotlin.cli.js.internal", + "org.jetbrains.kotlin.cli.jvm", + "org.jetbrains.kotlin.cli.jvm.compiler", + "org.jetbrains.kotlin.cli.jvm.config", + "org.jetbrains.kotlin.cli.jvm.index", + "org.jetbrains.kotlin.cli.jvm.javac", + "org.jetbrains.kotlin.cli.jvm.modules", + "org.jetbrains.kotlin.cli.jvm.plugins", + "org.jetbrains.kotlin.cli.metadata", + "org.jetbrains.kotlin.codegen", + "org.jetbrains.kotlin.codegen.binding", + "org.jetbrains.kotlin.codegen.context", + "org.jetbrains.kotlin.codegen.coroutines", + "org.jetbrains.kotlin.codegen.extensions", + "org.jetbrains.kotlin.codegen.inline", + "org.jetbrains.kotlin.codegen.inline.coroutines", + "org.jetbrains.kotlin.codegen.intrinsics", + "org.jetbrains.kotlin.codegen.optimization", + "org.jetbrains.kotlin.codegen.optimization.boxing", + "org.jetbrains.kotlin.codegen.optimization.common", + "org.jetbrains.kotlin.codegen.optimization.fixStack", + "org.jetbrains.kotlin.codegen.optimization.nullCheck", + "org.jetbrains.kotlin.codegen.optimization.transformer", + "org.jetbrains.kotlin.codegen.pseudoInsns", + "org.jetbrains.kotlin.codegen.range", + "org.jetbrains.kotlin.codegen.range.comparison", + "org.jetbrains.kotlin.codegen.range.forLoop", + "org.jetbrains.kotlin.codegen.range.inExpression", + "org.jetbrains.kotlin.codegen.serialization", + "org.jetbrains.kotlin.codegen.signature", + "org.jetbrains.kotlin.codegen.state", + "org.jetbrains.kotlin.codegen.when", + "org.jetbrains.kotlin.compiler.plugin", + "org.jetbrains.kotlin.compilerRunner", + "org.jetbrains.kotlin.config", + "org.jetbrains.kotlin.container", + "org.jetbrains.kotlin.context", + "org.jetbrains.kotlin.contracts", + "org.jetbrains.kotlin.contracts.description", + "org.jetbrains.kotlin.contracts.description.expressions", + "org.jetbrains.kotlin.contracts.interpretation", + "org.jetbrains.kotlin.contracts.model", + "org.jetbrains.kotlin.contracts.model.functors", + "org.jetbrains.kotlin.contracts.model.structure", + "org.jetbrains.kotlin.contracts.model.visitors", + "org.jetbrains.kotlin.contracts.parsing", + "org.jetbrains.kotlin.contracts.parsing.effects", + "org.jetbrains.kotlin.coroutines", + "org.jetbrains.kotlin.descriptors", + "org.jetbrains.kotlin.descriptors.annotations", + "org.jetbrains.kotlin.descriptors.deserialization", + "org.jetbrains.kotlin.descriptors.impl", + "org.jetbrains.kotlin.descriptors.java", + "org.jetbrains.kotlin.descriptors.konan", + "org.jetbrains.kotlin.descriptors.konan.impl", + "org.jetbrains.kotlin.descriptors.runtime.components", + "org.jetbrains.kotlin.descriptors.runtime.structure", + "org.jetbrains.kotlin.descriptors.synthetic", + "org.jetbrains.kotlin.diagnostics", + "org.jetbrains.kotlin.diagnostics.rendering", + "org.jetbrains.kotlin.extensions", + "org.jetbrains.kotlin.extensions.internal", + "org.jetbrains.kotlin.fileClasses", + "org.jetbrains.kotlin.fir", + "org.jetbrains.kotlin.fir.analysis", + "org.jetbrains.kotlin.fir.analysis.cfa", + "org.jetbrains.kotlin.fir.analysis.checkers", + "org.jetbrains.kotlin.fir.analysis.checkers.cfa", + "org.jetbrains.kotlin.fir.analysis.checkers.context", + "org.jetbrains.kotlin.fir.analysis.checkers.declaration", + "org.jetbrains.kotlin.fir.analysis.checkers.expression", + "org.jetbrains.kotlin.fir.analysis.checkers.extended", + "org.jetbrains.kotlin.fir.analysis.collectors", + "org.jetbrains.kotlin.fir.analysis.collectors.components", + "org.jetbrains.kotlin.fir.analysis.diagnostics", + "org.jetbrains.kotlin.fir.analysis.extensions", + "org.jetbrains.kotlin.fir.backend", + "org.jetbrains.kotlin.fir.backend.evaluate", + "org.jetbrains.kotlin.fir.backend.generators", + "org.jetbrains.kotlin.fir.backend.jvm", + "org.jetbrains.kotlin.fir.builder", + "org.jetbrains.kotlin.fir.caches", + "org.jetbrains.kotlin.fir.checkers", + "org.jetbrains.kotlin.fir.contracts", + "org.jetbrains.kotlin.fir.contracts.builder", + "org.jetbrains.kotlin.fir.contracts.description", + "org.jetbrains.kotlin.fir.contracts.impl", + "org.jetbrains.kotlin.fir.declarations", + "org.jetbrains.kotlin.fir.declarations.builder", + "org.jetbrains.kotlin.fir.declarations.impl", + "org.jetbrains.kotlin.fir.declarations.synthetic", + "org.jetbrains.kotlin.fir.descriptors", + "org.jetbrains.kotlin.fir.deserialization", + "org.jetbrains.kotlin.fir.diagnostics", + "org.jetbrains.kotlin.fir.expressions", + "org.jetbrains.kotlin.fir.expressions.builder", + "org.jetbrains.kotlin.fir.expressions.impl", + "org.jetbrains.kotlin.fir.extensions", + "org.jetbrains.kotlin.fir.extensions.predicate", + "org.jetbrains.kotlin.fir.impl", + "org.jetbrains.kotlin.fir.java", + "org.jetbrains.kotlin.fir.java.declarations", + "org.jetbrains.kotlin.fir.java.deserialization", + "org.jetbrains.kotlin.fir.java.enhancement", + "org.jetbrains.kotlin.fir.java.scopes", + "org.jetbrains.kotlin.fir.lazy", + "org.jetbrains.kotlin.fir.lightTree", + "org.jetbrains.kotlin.fir.lightTree.converter", + "org.jetbrains.kotlin.fir.lightTree.fir", + "org.jetbrains.kotlin.fir.lightTree.fir.modifier", + "org.jetbrains.kotlin.fir.references", + "org.jetbrains.kotlin.fir.references.builder", + "org.jetbrains.kotlin.fir.references.impl", + "org.jetbrains.kotlin.fir.resolve", + "org.jetbrains.kotlin.fir.resolve.calls", + "org.jetbrains.kotlin.fir.resolve.calls.jvm", + "org.jetbrains.kotlin.fir.resolve.calls.tower", + "org.jetbrains.kotlin.fir.resolve.dfa", + "org.jetbrains.kotlin.fir.resolve.dfa.cfg", + "org.jetbrains.kotlin.fir.resolve.dfa.contracts", + "org.jetbrains.kotlin.fir.resolve.diagnostics", + "org.jetbrains.kotlin.fir.resolve.inference", + "org.jetbrains.kotlin.fir.resolve.inference.model", + "org.jetbrains.kotlin.fir.resolve.providers", + "org.jetbrains.kotlin.fir.resolve.providers.impl", + "org.jetbrains.kotlin.fir.resolve.scopes", + "org.jetbrains.kotlin.fir.resolve.substitution", + "org.jetbrains.kotlin.fir.resolve.transformers", + "org.jetbrains.kotlin.fir.resolve.transformers.body.resolve", + "org.jetbrains.kotlin.fir.resolve.transformers.contracts", + "org.jetbrains.kotlin.fir.resolve.transformers.plugin", + "org.jetbrains.kotlin.fir.scopes", + "org.jetbrains.kotlin.fir.scopes.impl", + "org.jetbrains.kotlin.fir.scopes.jvm", + "org.jetbrains.kotlin.fir.serialization", + "org.jetbrains.kotlin.fir.serialization.constant", + "org.jetbrains.kotlin.fir.session", + "org.jetbrains.kotlin.fir.signaturer", + "org.jetbrains.kotlin.fir.symbols", + "org.jetbrains.kotlin.fir.symbols.impl", + "org.jetbrains.kotlin.fir.types", + "org.jetbrains.kotlin.fir.types.builder", + "org.jetbrains.kotlin.fir.types.impl", + "org.jetbrains.kotlin.fir.types.jvm", + "org.jetbrains.kotlin.fir.utils", + "org.jetbrains.kotlin.fir.visitors", + "org.jetbrains.kotlin.frontend.di", + "org.jetbrains.kotlin.frontend.java.di", + "org.jetbrains.kotlin.frontend.js.di", + "org.jetbrains.kotlin.idea", + "org.jetbrains.kotlin.incremental", + "org.jetbrains.kotlin.incremental.components", + "org.jetbrains.kotlin.incremental.js", + "org.jetbrains.kotlin.incremental.multiproject", + "org.jetbrains.kotlin.incremental.parsing", + "org.jetbrains.kotlin.incremental.snapshots", + "org.jetbrains.kotlin.incremental.storage", + "org.jetbrains.kotlin.incremental.util", + "org.jetbrains.kotlin.inline", + "org.jetbrains.kotlin.ir", + "org.jetbrains.kotlin.ir.backend.js", + "org.jetbrains.kotlin.ir.backend.js.export", + "org.jetbrains.kotlin.ir.backend.js.ir", + "org.jetbrains.kotlin.ir.backend.js.lower", + "org.jetbrains.kotlin.ir.backend.js.lower.calls", + "org.jetbrains.kotlin.ir.backend.js.lower.cleanup", + "org.jetbrains.kotlin.ir.backend.js.lower.coroutines", + "org.jetbrains.kotlin.ir.backend.js.lower.inline", + "org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir", + "org.jetbrains.kotlin.ir.backend.js.transformers.irToJs", + "org.jetbrains.kotlin.ir.backend.js.utils", + "org.jetbrains.kotlin.ir.backend.jvm", + "org.jetbrains.kotlin.ir.backend.jvm.serialization", + "org.jetbrains.kotlin.ir.builders", + "org.jetbrains.kotlin.ir.builders.declarations", + "org.jetbrains.kotlin.ir.declarations", + "org.jetbrains.kotlin.ir.declarations.impl", + "org.jetbrains.kotlin.ir.declarations.lazy", + "org.jetbrains.kotlin.ir.declarations.persistent", + "org.jetbrains.kotlin.ir.declarations.persistent.carriers", + "org.jetbrains.kotlin.ir.descriptors", + "org.jetbrains.kotlin.ir.expressions", + "org.jetbrains.kotlin.ir.expressions.impl", + "org.jetbrains.kotlin.ir.expressions.persistent", + "org.jetbrains.kotlin.ir.interpreter", + "org.jetbrains.kotlin.ir.interpreter.builtins", + "org.jetbrains.kotlin.ir.interpreter.exceptions", + "org.jetbrains.kotlin.ir.interpreter.intrinsics", + "org.jetbrains.kotlin.ir.interpreter.stack", + "org.jetbrains.kotlin.ir.interpreter.state", + "org.jetbrains.kotlin.ir.linkage", + "org.jetbrains.kotlin.ir.overrides", + "org.jetbrains.kotlin.ir.symbols", + "org.jetbrains.kotlin.ir.symbols.impl", + "org.jetbrains.kotlin.ir.types", + "org.jetbrains.kotlin.ir.types.impl", + "org.jetbrains.kotlin.ir.util", + "org.jetbrains.kotlin.ir.visitors", + "org.jetbrains.kotlin.javac", + "org.jetbrains.kotlin.javac.components", + "org.jetbrains.kotlin.javac.resolve", + "org.jetbrains.kotlin.javac.wrappers.symbols", + "org.jetbrains.kotlin.javac.wrappers.trees", + "org.jetbrains.kotlin.js", + "org.jetbrains.kotlin.js.analyze", + "org.jetbrains.kotlin.js.analyzer", + "org.jetbrains.kotlin.js.backend", + "org.jetbrains.kotlin.js.backend.ast", + "org.jetbrains.kotlin.js.backend.ast.metadata", + "org.jetbrains.kotlin.js.common", + "org.jetbrains.kotlin.js.config", + "org.jetbrains.kotlin.js.coroutine", + "org.jetbrains.kotlin.js.dce", + "org.jetbrains.kotlin.js.descriptorUtils", + "org.jetbrains.kotlin.js.facade", + "org.jetbrains.kotlin.js.facade.exceptions", + "org.jetbrains.kotlin.js.inline", + "org.jetbrains.kotlin.js.inline.clean", + "org.jetbrains.kotlin.js.inline.context", + "org.jetbrains.kotlin.js.inline.util", + "org.jetbrains.kotlin.js.inline.util.collectors", + "org.jetbrains.kotlin.js.inline.util.rewriters", + "org.jetbrains.kotlin.js.naming", + "org.jetbrains.kotlin.js.parser", + "org.jetbrains.kotlin.js.parser.sourcemaps", + "org.jetbrains.kotlin.js.patterns", + "org.jetbrains.kotlin.js.patterns.typePredicates", + "org.jetbrains.kotlin.js.resolve", + "org.jetbrains.kotlin.js.resolve.diagnostics", + "org.jetbrains.kotlin.js.sourceMap", + "org.jetbrains.kotlin.js.translate.callTranslator", + "org.jetbrains.kotlin.js.translate.context", + "org.jetbrains.kotlin.js.translate.context.generator", + "org.jetbrains.kotlin.js.translate.declaration", + "org.jetbrains.kotlin.js.translate.expression", + "org.jetbrains.kotlin.js.translate.extensions", + "org.jetbrains.kotlin.js.translate.general", + "org.jetbrains.kotlin.js.translate.initializer", + "org.jetbrains.kotlin.js.translate.intrinsic", + "org.jetbrains.kotlin.js.translate.intrinsic.functions", + "org.jetbrains.kotlin.js.translate.intrinsic.functions.basic", + "org.jetbrains.kotlin.js.translate.intrinsic.functions.factories", + "org.jetbrains.kotlin.js.translate.intrinsic.objects", + "org.jetbrains.kotlin.js.translate.intrinsic.operation", + "org.jetbrains.kotlin.js.translate.operation", + "org.jetbrains.kotlin.js.translate.reference", + "org.jetbrains.kotlin.js.translate.test", + "org.jetbrains.kotlin.js.translate.utils", + "org.jetbrains.kotlin.js.translate.utils.jsAstUtils", + "org.jetbrains.kotlin.js.translate.utils.mutator", + "org.jetbrains.kotlin.js.util", + "org.jetbrains.kotlin.kapt3.diagnostic", + "org.jetbrains.kotlin.kdoc.lexer", + "org.jetbrains.kotlin.kdoc.parser", + "org.jetbrains.kotlin.kdoc.psi.api", + "org.jetbrains.kotlin.kdoc.psi.impl", + "org.jetbrains.kotlin.konan", + "org.jetbrains.kotlin.konan.file", + "org.jetbrains.kotlin.konan.library", + "org.jetbrains.kotlin.konan.properties", + "org.jetbrains.kotlin.konan.target", + "org.jetbrains.kotlin.konan.util", + "org.jetbrains.kotlin.lexer", + "org.jetbrains.kotlin.library", + "org.jetbrains.kotlin.library.impl", + "org.jetbrains.kotlin.library.metadata", + "org.jetbrains.kotlin.library.resolver", + "org.jetbrains.kotlin.library.resolver.impl", + "org.jetbrains.kotlin.load.java", + "org.jetbrains.kotlin.load.java.components", + "org.jetbrains.kotlin.load.java.descriptors", + "org.jetbrains.kotlin.load.java.lazy", + "org.jetbrains.kotlin.load.java.lazy.descriptors", + "org.jetbrains.kotlin.load.java.lazy.types", + "org.jetbrains.kotlin.load.java.sam", + "org.jetbrains.kotlin.load.java.sources", + "org.jetbrains.kotlin.load.java.structure", + "org.jetbrains.kotlin.load.java.structure.impl", + "org.jetbrains.kotlin.load.java.structure.impl.classFiles", + "org.jetbrains.kotlin.load.java.typeEnhancement", + "org.jetbrains.kotlin.load.kotlin", + "org.jetbrains.kotlin.load.kotlin.header", + "org.jetbrains.kotlin.load.kotlin.incremental", + "org.jetbrains.kotlin.load.kotlin.incremental.components", + "org.jetbrains.kotlin.metadata", + "org.jetbrains.kotlin.metadata.builtins", + "org.jetbrains.kotlin.metadata.deserialization", + "org.jetbrains.kotlin.metadata.java", + "org.jetbrains.kotlin.metadata.js", + "org.jetbrains.kotlin.metadata.jvm", + "org.jetbrains.kotlin.metadata.jvm.deserialization", + "org.jetbrains.kotlin.metadata.jvm.serialization", + "org.jetbrains.kotlin.metadata.serialization", + "org.jetbrains.kotlin.modules", + "org.jetbrains.kotlin.name", + "org.jetbrains.kotlin.parsing", + "org.jetbrains.kotlin.platform", + "org.jetbrains.kotlin.platform.js", + "org.jetbrains.kotlin.platform.jvm", + "org.jetbrains.kotlin.platform.konan", + "org.jetbrains.kotlin.progress", + "org.jetbrains.kotlin.progress.experimental", + "org.jetbrains.kotlin.protobuf", + "org.jetbrains.kotlin.psi", + "org.jetbrains.kotlin.psi.addRemoveModifier", + "org.jetbrains.kotlin.psi.codeFragmentUtil", + "org.jetbrains.kotlin.psi.debugText", + "org.jetbrains.kotlin.psi.findDocComment", + "org.jetbrains.kotlin.psi.psiUtil", + "org.jetbrains.kotlin.psi.stubs", + "org.jetbrains.kotlin.psi.stubs.elements", + "org.jetbrains.kotlin.psi.stubs.impl", + "org.jetbrains.kotlin.psi.synthetics", + "org.jetbrains.kotlin.psi.typeRefHelpers", + "org.jetbrains.kotlin.psi2ir", + "org.jetbrains.kotlin.psi2ir.generators", + "org.jetbrains.kotlin.psi2ir.intermediate", + "org.jetbrains.kotlin.psi2ir.transformations", + "org.jetbrains.kotlin.renderer", + "org.jetbrains.kotlin.resolve", + "org.jetbrains.kotlin.resolve.annotations", + "org.jetbrains.kotlin.resolve.bindingContextUtil", + "org.jetbrains.kotlin.resolve.calls", + "org.jetbrains.kotlin.resolve.calls.callResolverUtil", + "org.jetbrains.kotlin.resolve.calls.callUtil", + "org.jetbrains.kotlin.resolve.calls.checkers", + "org.jetbrains.kotlin.resolve.calls.components", + "org.jetbrains.kotlin.resolve.calls.context", + "org.jetbrains.kotlin.resolve.calls.inference", + "org.jetbrains.kotlin.resolve.calls.inference.components", + "org.jetbrains.kotlin.resolve.calls.inference.constraintPosition", + "org.jetbrains.kotlin.resolve.calls.inference.model", + "org.jetbrains.kotlin.resolve.calls.model", + "org.jetbrains.kotlin.resolve.calls.resolvedCallUtil", + "org.jetbrains.kotlin.resolve.calls.results", + "org.jetbrains.kotlin.resolve.calls.smartcasts", + "org.jetbrains.kotlin.resolve.calls.tasks", + "org.jetbrains.kotlin.resolve.calls.tower", + "org.jetbrains.kotlin.resolve.calls.util", + "org.jetbrains.kotlin.resolve.checkers", + "org.jetbrains.kotlin.resolve.constants", + "org.jetbrains.kotlin.resolve.constants.evaluate", + "org.jetbrains.kotlin.resolve.deprecation", + "org.jetbrains.kotlin.resolve.descriptorUtil", + "org.jetbrains.kotlin.resolve.diagnostics", + "org.jetbrains.kotlin.resolve.extensions", + "org.jetbrains.kotlin.resolve.inline", + "org.jetbrains.kotlin.resolve.jvm", + "org.jetbrains.kotlin.resolve.jvm.annotations", + "org.jetbrains.kotlin.resolve.jvm.checkers", + "org.jetbrains.kotlin.resolve.jvm.diagnostics", + "org.jetbrains.kotlin.resolve.jvm.extensions", + "org.jetbrains.kotlin.resolve.jvm.jvmSignature", + "org.jetbrains.kotlin.resolve.jvm.kotlinSignature", + "org.jetbrains.kotlin.resolve.jvm.modules", + "org.jetbrains.kotlin.resolve.jvm.multiplatform", + "org.jetbrains.kotlin.resolve.jvm.platform", + "org.jetbrains.kotlin.resolve.konan.diagnostics", + "org.jetbrains.kotlin.resolve.konan.platform", + "org.jetbrains.kotlin.resolve.lazy", + "org.jetbrains.kotlin.resolve.lazy.data", + "org.jetbrains.kotlin.resolve.lazy.declarations", + "org.jetbrains.kotlin.resolve.lazy.descriptors", + "org.jetbrains.kotlin.resolve.multiplatform", + "org.jetbrains.kotlin.resolve.repl", + "org.jetbrains.kotlin.resolve.sam", + "org.jetbrains.kotlin.resolve.scopes", + "org.jetbrains.kotlin.resolve.scopes.receivers", + "org.jetbrains.kotlin.resolve.scopes.synthetic", + "org.jetbrains.kotlin.resolve.scopes.utils", + "org.jetbrains.kotlin.resolve.source", + "org.jetbrains.kotlin.resolve.typeBinding", + "org.jetbrains.kotlin.scripting", + "org.jetbrains.kotlin.scripting.compiler.plugin", + "org.jetbrains.kotlin.scripting.compiler.plugin.definitions", + "org.jetbrains.kotlin.scripting.compiler.plugin.dependencies", + "org.jetbrains.kotlin.scripting.compiler.plugin.extensions", + "org.jetbrains.kotlin.scripting.compiler.plugin.impl", + "org.jetbrains.kotlin.scripting.compiler.plugin.repl", + "org.jetbrains.kotlin.scripting.compiler.plugin.repl.configuration", + "org.jetbrains.kotlin.scripting.compiler.plugin.repl.messages", + "org.jetbrains.kotlin.scripting.compiler.plugin.repl.reader", + "org.jetbrains.kotlin.scripting.compiler.plugin.repl.writer", + "org.jetbrains.kotlin.scripting.configuration", + "org.jetbrains.kotlin.scripting.definitions", + "org.jetbrains.kotlin.scripting.extensions", + "org.jetbrains.kotlin.scripting.repl.js", + "org.jetbrains.kotlin.scripting.resolve", + "org.jetbrains.kotlin.serialization", + "org.jetbrains.kotlin.serialization.builtins", + "org.jetbrains.kotlin.serialization.deserialization", + "org.jetbrains.kotlin.serialization.deserialization.builtins", + "org.jetbrains.kotlin.serialization.deserialization.descriptors", + "org.jetbrains.kotlin.serialization.js", + "org.jetbrains.kotlin.serialization.js.ast", + "org.jetbrains.kotlin.serialization.konan", + "org.jetbrains.kotlin.serialization.konan.impl", + "org.jetbrains.kotlin.storage", + "org.jetbrains.kotlin.synthetic", + "org.jetbrains.kotlin.type", + "org.jetbrains.kotlin.types", + "org.jetbrains.kotlin.types.checker", + "org.jetbrains.kotlin.types.error", + "org.jetbrains.kotlin.types.expressions", + "org.jetbrains.kotlin.types.expressions.typeInfoFactory", + "org.jetbrains.kotlin.types.expressions.unqualifiedSuper", + "org.jetbrains.kotlin.types.model", + "org.jetbrains.kotlin.types.refinement", + "org.jetbrains.kotlin.types.typeUtil", + "org.jetbrains.kotlin.types.typesApproximation", + "org.jetbrains.kotlin.util", + "org.jetbrains.kotlin.util.capitalizeDecapitalize", + "org.jetbrains.kotlin.util.collectionUtils", + "org.jetbrains.kotlin.util.javaslang", + "org.jetbrains.kotlin.util.slicedMap", + "org.jetbrains.kotlin.utils", + "org.jetbrains.kotlin.utils.addToStdlib", + "org.jetbrains.kotlin.utils.concurrent.block", + "org.jetbrains.kotlin.utils.fileUtils", + "org.jetbrains.kotlin.utils.intellij", + "org.jetbrains.kotlin.utils.kapt", + "org.jetbrains.kotlin.utils.repl", + "org.jetbrains.kotlin.utils.strings", + "org.jetbrains.kotlin.wasm.ir", + "org.jetbrains.kotlin.wasm.ir.convertors" + ], + "com.android.tools.external.org-jetbrains:uast": [ + "com.android.tools.external.org.jetbrains.uast", + "org.jetbrains.uast", + "org.jetbrains.uast.analysis", + "org.jetbrains.uast.evaluation", + "org.jetbrains.uast.expressions", + "org.jetbrains.uast.generate", + "org.jetbrains.uast.internal", + "org.jetbrains.uast.java", + "org.jetbrains.uast.java.analysis", + "org.jetbrains.uast.java.declarations", + "org.jetbrains.uast.java.expressions", + "org.jetbrains.uast.java.generate", + "org.jetbrains.uast.java.internal", + "org.jetbrains.uast.java.kinds", + "org.jetbrains.uast.kotlin", + "org.jetbrains.uast.kotlin.declarations", + "org.jetbrains.uast.kotlin.evaluation", + "org.jetbrains.uast.kotlin.expressions", + "org.jetbrains.uast.kotlin.internal", + "org.jetbrains.uast.kotlin.kinds", + "org.jetbrains.uast.kotlin.psi", + "org.jetbrains.uast.psi", + "org.jetbrains.uast.util", + "org.jetbrains.uast.values", + "org.jetbrains.uast.visitor", + "test.pkg" + ], + "com.android.tools.layoutlib:layoutlib-api": [ + "com.android.ide.common.rendering.api", + "com.android.resources" + ], + "com.android.tools.lint:lint-api": [ + "com.android.tools.lint.client.api", + "com.android.tools.lint.detector.api", + "com.android.tools.lint.detector.api.interprocedural", + "com.android.tools.lint.helpers" + ], + "com.android.tools.lint:lint-checks": [ + "com.android.tools.lint.checks" + ], + "com.android.tools.lint:lint-model": [ + "com.android.tools.lint.model" + ], + "com.android.tools:annotations": [ + "com.android.annotations", + "com.android.annotations.concurrency" + ], + "com.android.tools:common": [ + "com.android", + "com.android.ide.common.blame", + "com.android.io", + "com.android.prefs", + "com.android.resources", + "com.android.sdklib", + "com.android.support", + "com.android.tools.proguard", + "com.android.utils", + "com.android.utils.concurrency", + "com.android.utils.cxx", + "com.android.utils.reflection", + "com.android.xml" + ], + "com.android.tools:dvlib": [ + "com.android.dvlib" + ], + "com.android.tools:repository": [ + "com.android.repository", + "com.android.repository.api", + "com.android.repository.impl.downloader", + "com.android.repository.impl.generated.generic.v1", + "com.android.repository.impl.generated.generic.v2", + "com.android.repository.impl.generated.v1", + "com.android.repository.impl.generated.v2", + "com.android.repository.impl.installer", + "com.android.repository.impl.manager", + "com.android.repository.impl.meta", + "com.android.repository.impl.sources", + "com.android.repository.impl.sources.generated.v1", + "com.android.repository.io", + "com.android.repository.io.impl", + "com.android.repository.util" + ], + "com.android.tools:sdk-common": [ + "com.android.ide.common.attribution", + "com.android.ide.common.blame", + "com.android.ide.common.blame.parser", + "com.android.ide.common.blame.parser.aapt", + "com.android.ide.common.blame.parser.util", + "com.android.ide.common.build", + "com.android.ide.common.caching", + "com.android.ide.common.fonts", + "com.android.ide.common.internal", + "com.android.ide.common.process", + "com.android.ide.common.rendering", + "com.android.ide.common.repository", + "com.android.ide.common.resources", + "com.android.ide.common.resources.configuration", + "com.android.ide.common.resources.sampledata", + "com.android.ide.common.resources.usage", + "com.android.ide.common.sdk", + "com.android.ide.common.signing", + "com.android.ide.common.symbols", + "com.android.ide.common.util", + "com.android.ide.common.vectordrawable", + "com.android.ide.common.workers", + "com.android.ide.common.xml", + "com.android.instantapp.provision", + "com.android.instantapp.run", + "com.android.instantapp.sdk", + "com.android.instantapp.utils", + "com.android.projectmodel", + "wireless.android.instantapps.sdk" + ], + "com.android.tools:sdklib": [ + "com.android.sdklib", + "com.android.sdklib.devices", + "com.android.sdklib.internal.avd", + "com.android.sdklib.internal.build", + "com.android.sdklib.internal.project", + "com.android.sdklib.repository", + "com.android.sdklib.repository.generated.addon.v1", + "com.android.sdklib.repository.generated.addon.v2", + "com.android.sdklib.repository.generated.common.v1", + "com.android.sdklib.repository.generated.common.v2", + "com.android.sdklib.repository.generated.repository.v1", + "com.android.sdklib.repository.generated.repository.v2", + "com.android.sdklib.repository.generated.sysimg.v1", + "com.android.sdklib.repository.generated.sysimg.v2", + "com.android.sdklib.repository.installer", + "com.android.sdklib.repository.legacy", + "com.android.sdklib.repository.legacy.descriptors", + "com.android.sdklib.repository.legacy.local", + "com.android.sdklib.repository.legacy.remote", + "com.android.sdklib.repository.legacy.remote.internal", + "com.android.sdklib.repository.legacy.remote.internal.archives", + "com.android.sdklib.repository.legacy.remote.internal.packages", + "com.android.sdklib.repository.legacy.remote.internal.sources", + "com.android.sdklib.repository.meta", + "com.android.sdklib.repository.sources", + "com.android.sdklib.repository.sources.generated.v1", + "com.android.sdklib.repository.sources.generated.v2", + "com.android.sdklib.repository.sources.generated.v3", + "com.android.sdklib.repository.sources.generated.v4", + "com.android.sdklib.repository.targets", + "com.android.sdklib.tool", + "com.android.sdklib.tool.sdkmanager", + "com.android.sdklib.util" + ], + "com.beust:jcommander": [ + "com.beust.jcommander", + "com.beust.jcommander.converters", + "com.beust.jcommander.defaultprovider", + "com.beust.jcommander.internal", + "com.beust.jcommander.validators" + ], + "com.github.bumptech.glide:annotations": [ + "com.bumptech.glide.annotation", + "com.bumptech.glide.annotation.compiler" + ], + "com.github.bumptech.glide:compiler": [ + "com.bumptech.glide.annotation.compiler", + "com.bumptech.glide.repackaged.com.google.common.base", + "com.bumptech.glide.repackaged.com.google.common.collect", + "com.bumptech.glide.repackaged.com.squareup.javapoet" + ], + "com.github.bumptech.glide:disklrucache": [ + "com.bumptech.glide.disklrucache" + ], + "com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework": [ + "com.google.android.apps.common.testing.accessibility.framework", + "com.google.android.apps.common.testing.accessibility.framework.integrations", + "com.google.android.apps.common.testing.accessibility.framework.integrations.espresso", + "com.googlecode.eyesfree.compat", + "com.googlecode.eyesfree.utils" + ], + "com.google.android.gms:strict-version-matcher-plugin": [ + "com.google.android.gms", + "com.google.android.gms.dependencies" + ], + "com.google.android:annotations": [ + "android.annotation" + ], + "com.google.auto.service:auto-service-annotations": [ "com.google.auto.service" ], "com.google.auto.value:auto-value-annotations": [ @@ -2771,6 +4380,9 @@ "com.google.j2objc:j2objc-annotations": [ "com.google.j2objc.annotations" ], + "com.google.jimfs:jimfs": [ + "com.google.common.jimfs" + ], "com.google.protobuf:protobuf-java": [ "com.google.protobuf", "com.google.protobuf.compiler" @@ -2896,6 +4508,35 @@ "com.squareup.kotlinpoet.jvm", "com.squareup.kotlinpoet.tags" ], + "com.sun.activation:javax.activation": [ + "com.sun.activation.registries", + "com.sun.activation.viewers", + "javax.activation" + ], + "com.sun.istack:istack-commons-runtime": [ + "com.sun.istack", + "com.sun.istack.localization", + "com.sun.istack.logging" + ], + "com.sun.xml.fastinfoset:FastInfoset": [ + "com.sun.xml.fastinfoset", + "com.sun.xml.fastinfoset.algorithm", + "com.sun.xml.fastinfoset.alphabet", + "com.sun.xml.fastinfoset.dom", + "com.sun.xml.fastinfoset.org.apache.xerces.util", + "com.sun.xml.fastinfoset.sax", + "com.sun.xml.fastinfoset.stax", + "com.sun.xml.fastinfoset.stax.events", + "com.sun.xml.fastinfoset.stax.factory", + "com.sun.xml.fastinfoset.stax.util", + "com.sun.xml.fastinfoset.tools", + "com.sun.xml.fastinfoset.util", + "com.sun.xml.fastinfoset.vocab", + "org.jvnet.fastinfoset", + "org.jvnet.fastinfoset.sax", + "org.jvnet.fastinfoset.sax.helpers", + "org.jvnet.fastinfoset.stax" + ], "commons-codec:commons-codec": [ "org.apache.commons.codec", "org.apache.commons.codec.binary", @@ -2912,6 +4553,10 @@ "org.apache.commons.io.monitor", "org.apache.commons.io.output" ], + "commons-logging:commons-logging": [ + "org.apache.commons.logging", + "org.apache.commons.logging.impl" + ], "io.grpc:grpc-api": [ "io.grpc" ], @@ -2941,11 +4586,22 @@ "io.xlate:yaml-json": [ "io.xlate.yamljson" ], + "jakarta.activation:jakarta.activation-api": [ + "javax.activation" + ], "jakarta.json:jakarta.json-api": [ "jakarta.json", "jakarta.json.spi", "jakarta.json.stream" ], + "jakarta.xml.bind:jakarta.xml.bind-api": [ + "javax.xml.bind", + "javax.xml.bind.annotation", + "javax.xml.bind.annotation.adapters", + "javax.xml.bind.attachment", + "javax.xml.bind.helpers", + "javax.xml.bind.util" + ], "javax.annotation:javax.annotation-api": [ "javax.annotation", "javax.annotation.security", @@ -3029,6 +4685,27 @@ "net.bytebuddy:byte-buddy-agent": [ "net.bytebuddy.agent" ], + "net.java.dev.jna:jna": [ + "com.sun.jna", + "com.sun.jna.internal", + "com.sun.jna.ptr", + "com.sun.jna.win32" + ], + "net.java.dev.jna:jna-platform": [ + "com.sun.jna.platform", + "com.sun.jna.platform.dnd", + "com.sun.jna.platform.linux", + "com.sun.jna.platform.mac", + "com.sun.jna.platform.unix", + "com.sun.jna.platform.unix.solaris", + "com.sun.jna.platform.win32", + "com.sun.jna.platform.win32.COM", + "com.sun.jna.platform.win32.COM.tlb", + "com.sun.jna.platform.win32.COM.tlb.imp", + "com.sun.jna.platform.win32.COM.util", + "com.sun.jna.platform.win32.COM.util.annotation", + "com.sun.jna.platform.wince" + ], "net.ltgt.gradle.incap:incap": [ "net.ltgt.gradle.incap" ], @@ -3079,6 +4756,127 @@ "org.stringtemplate.v4.gui", "org.stringtemplate.v4.misc" ], + "org.apache.commons:commons-compress": [ + "org.apache.commons.compress", + "org.apache.commons.compress.archivers", + "org.apache.commons.compress.archivers.ar", + "org.apache.commons.compress.archivers.arj", + "org.apache.commons.compress.archivers.cpio", + "org.apache.commons.compress.archivers.dump", + "org.apache.commons.compress.archivers.examples", + "org.apache.commons.compress.archivers.jar", + "org.apache.commons.compress.archivers.sevenz", + "org.apache.commons.compress.archivers.tar", + "org.apache.commons.compress.archivers.zip", + "org.apache.commons.compress.changes", + "org.apache.commons.compress.compressors", + "org.apache.commons.compress.compressors.brotli", + "org.apache.commons.compress.compressors.bzip2", + "org.apache.commons.compress.compressors.deflate", + "org.apache.commons.compress.compressors.deflate64", + "org.apache.commons.compress.compressors.gzip", + "org.apache.commons.compress.compressors.lz4", + "org.apache.commons.compress.compressors.lz77support", + "org.apache.commons.compress.compressors.lzma", + "org.apache.commons.compress.compressors.lzw", + "org.apache.commons.compress.compressors.pack200", + "org.apache.commons.compress.compressors.snappy", + "org.apache.commons.compress.compressors.xz", + "org.apache.commons.compress.compressors.z", + "org.apache.commons.compress.compressors.zstandard", + "org.apache.commons.compress.parallel", + "org.apache.commons.compress.utils" + ], + "org.apache.httpcomponents:httpclient": [ + "org.apache.http.auth", + "org.apache.http.auth.params", + "org.apache.http.client", + "org.apache.http.client.config", + "org.apache.http.client.entity", + "org.apache.http.client.methods", + "org.apache.http.client.params", + "org.apache.http.client.protocol", + "org.apache.http.client.utils", + "org.apache.http.conn", + "org.apache.http.conn.params", + "org.apache.http.conn.routing", + "org.apache.http.conn.scheme", + "org.apache.http.conn.socket", + "org.apache.http.conn.ssl", + "org.apache.http.conn.util", + "org.apache.http.cookie", + "org.apache.http.cookie.params", + "org.apache.http.impl.auth", + "org.apache.http.impl.client", + "org.apache.http.impl.conn", + "org.apache.http.impl.conn.tsccm", + "org.apache.http.impl.cookie", + "org.apache.http.impl.execchain" + ], + "org.apache.httpcomponents:httpcore": [ + "org.apache.http", + "org.apache.http.annotation", + "org.apache.http.concurrent", + "org.apache.http.config", + "org.apache.http.entity", + "org.apache.http.impl", + "org.apache.http.impl.bootstrap", + "org.apache.http.impl.entity", + "org.apache.http.impl.io", + "org.apache.http.impl.pool", + "org.apache.http.io", + "org.apache.http.message", + "org.apache.http.params", + "org.apache.http.pool", + "org.apache.http.protocol", + "org.apache.http.ssl", + "org.apache.http.util" + ], + "org.apache.httpcomponents:httpmime": [ + "org.apache.http.entity.mime", + "org.apache.http.entity.mime.content" + ], + "org.bouncycastle:bcpkix-jdk15on": [ + "org.bouncycastle.cert", + "org.bouncycastle.cert.bc", + "org.bouncycastle.cert.cmp", + "org.bouncycastle.cert.crmf", + "org.bouncycastle.cert.crmf.bc", + "org.bouncycastle.cert.crmf.jcajce", + "org.bouncycastle.cert.dane", + "org.bouncycastle.cert.dane.fetcher", + "org.bouncycastle.cert.jcajce", + "org.bouncycastle.cert.ocsp", + "org.bouncycastle.cert.ocsp.jcajce", + "org.bouncycastle.cert.path", + "org.bouncycastle.cert.path.validations", + "org.bouncycastle.cert.selector", + "org.bouncycastle.cert.selector.jcajce", + "org.bouncycastle.cms", + "org.bouncycastle.cms.bc", + "org.bouncycastle.cms.jcajce", + "org.bouncycastle.dvcs", + "org.bouncycastle.eac", + "org.bouncycastle.eac.jcajce", + "org.bouncycastle.eac.operator", + "org.bouncycastle.eac.operator.jcajce", + "org.bouncycastle.mozilla", + "org.bouncycastle.mozilla.jcajce", + "org.bouncycastle.openssl", + "org.bouncycastle.openssl.bc", + "org.bouncycastle.openssl.jcajce", + "org.bouncycastle.operator", + "org.bouncycastle.operator.bc", + "org.bouncycastle.operator.jcajce", + "org.bouncycastle.pkcs", + "org.bouncycastle.pkcs.bc", + "org.bouncycastle.pkcs.jcajce", + "org.bouncycastle.pkix", + "org.bouncycastle.pkix.jcajce", + "org.bouncycastle.tsp", + "org.bouncycastle.tsp.cms", + "org.bouncycastle.voms" + ], "org.bouncycastle:bcprov-jdk15on": [ "org.bouncycastle", "org.bouncycastle.asn1", @@ -3281,6 +5079,38 @@ "org.eclipse.parsson", "org.eclipse.parsson.api" ], + "org.glassfish.jaxb:jaxb-runtime": [ + "com.sun.xml.bind", + "com.sun.xml.bind.annotation", + "com.sun.xml.bind.api", + "com.sun.xml.bind.api.impl", + "com.sun.xml.bind.marshaller", + "com.sun.xml.bind.unmarshaller", + "com.sun.xml.bind.util", + "com.sun.xml.bind.v2", + "com.sun.xml.bind.v2.bytecode", + "com.sun.xml.bind.v2.model.annotation", + "com.sun.xml.bind.v2.model.core", + "com.sun.xml.bind.v2.model.impl", + "com.sun.xml.bind.v2.model.nav", + "com.sun.xml.bind.v2.model.runtime", + "com.sun.xml.bind.v2.model.util", + "com.sun.xml.bind.v2.runtime", + "com.sun.xml.bind.v2.runtime.output", + "com.sun.xml.bind.v2.runtime.property", + "com.sun.xml.bind.v2.runtime.reflect", + "com.sun.xml.bind.v2.runtime.reflect.opt", + "com.sun.xml.bind.v2.runtime.unmarshaller", + "com.sun.xml.bind.v2.schemagen", + "com.sun.xml.bind.v2.schemagen.episode", + "com.sun.xml.bind.v2.schemagen.xmlschema", + "com.sun.xml.bind.v2.util" + ], + "org.glassfish.jaxb:txw2": [ + "com.sun.xml.txw2", + "com.sun.xml.txw2.annotation", + "com.sun.xml.txw2.output" + ], "org.hamcrest:hamcrest-core": [ "org.hamcrest", "org.hamcrest.core", @@ -4220,6 +6050,10 @@ "org.intellij.lang.annotations", "org.jetbrains.annotations" ], + "org.jvnet.staxex:stax-ex": [ + "org.jvnet.staxex", + "org.jvnet.staxex.util" + ], "org.mockito.kotlin:mockito-kotlin": [ "org.mockito.kotlin", "org.mockito.kotlin.internal" @@ -4388,6 +6222,57 @@ "org.robolectric:utils-reflector": [ "org.robolectric.util.reflector" ], + "org.smali:baksmali": [ + "org.jf.baksmali", + "org.jf.baksmali.Adaptors", + "org.jf.baksmali.Adaptors.Debug", + "org.jf.baksmali.Adaptors.EncodedValue", + "org.jf.baksmali.Adaptors.Format", + "org.jf.baksmali.Renderers" + ], + "org.smali:dexlib2": [ + "org.jf.dexlib2", + "org.jf.dexlib2.analysis", + "org.jf.dexlib2.analysis.reflection", + "org.jf.dexlib2.analysis.reflection.util", + "org.jf.dexlib2.analysis.util", + "org.jf.dexlib2.base", + "org.jf.dexlib2.base.reference", + "org.jf.dexlib2.base.value", + "org.jf.dexlib2.builder", + "org.jf.dexlib2.builder.debug", + "org.jf.dexlib2.builder.instruction", + "org.jf.dexlib2.dexbacked", + "org.jf.dexlib2.dexbacked.instruction", + "org.jf.dexlib2.dexbacked.raw", + "org.jf.dexlib2.dexbacked.raw.util", + "org.jf.dexlib2.dexbacked.reference", + "org.jf.dexlib2.dexbacked.util", + "org.jf.dexlib2.dexbacked.value", + "org.jf.dexlib2.iface", + "org.jf.dexlib2.iface.debug", + "org.jf.dexlib2.iface.instruction", + "org.jf.dexlib2.iface.instruction.formats", + "org.jf.dexlib2.iface.reference", + "org.jf.dexlib2.iface.value", + "org.jf.dexlib2.immutable", + "org.jf.dexlib2.immutable.debug", + "org.jf.dexlib2.immutable.instruction", + "org.jf.dexlib2.immutable.reference", + "org.jf.dexlib2.immutable.util", + "org.jf.dexlib2.immutable.value", + "org.jf.dexlib2.rewriter", + "org.jf.dexlib2.util", + "org.jf.dexlib2.writer", + "org.jf.dexlib2.writer.builder", + "org.jf.dexlib2.writer.io", + "org.jf.dexlib2.writer.pool", + "org.jf.dexlib2.writer.util" + ], + "org.smali:util": [ + "org.jf.util", + "org.jf.util.jcommander" + ], "org.snakeyaml:snakeyaml-engine": [ "org.snakeyaml.engine.external.com.google.gdata.util.common.base", "org.snakeyaml.engine.v2.api", @@ -4410,6 +6295,79 @@ "org.snakeyaml.engine.v2.schema", "org.snakeyaml.engine.v2.serializer", "org.snakeyaml.engine.v2.tokens" + ], + "xerces:xercesImpl": [ + "org.apache.html.dom", + "org.apache.wml", + "org.apache.wml.dom", + "org.apache.xerces.dom", + "org.apache.xerces.dom.events", + "org.apache.xerces.dom3.as", + "org.apache.xerces.impl", + "org.apache.xerces.impl.dtd", + "org.apache.xerces.impl.dtd.models", + "org.apache.xerces.impl.dv", + "org.apache.xerces.impl.dv.dtd", + "org.apache.xerces.impl.dv.util", + "org.apache.xerces.impl.dv.xs", + "org.apache.xerces.impl.io", + "org.apache.xerces.impl.msg", + "org.apache.xerces.impl.validation", + "org.apache.xerces.impl.xpath", + "org.apache.xerces.impl.xpath.regex", + "org.apache.xerces.impl.xs", + "org.apache.xerces.impl.xs.identity", + "org.apache.xerces.impl.xs.models", + "org.apache.xerces.impl.xs.opti", + "org.apache.xerces.impl.xs.traversers", + "org.apache.xerces.impl.xs.util", + "org.apache.xerces.jaxp", + "org.apache.xerces.jaxp.datatype", + "org.apache.xerces.jaxp.validation", + "org.apache.xerces.parsers", + "org.apache.xerces.stax", + "org.apache.xerces.stax.events", + "org.apache.xerces.util", + "org.apache.xerces.xinclude", + "org.apache.xerces.xni", + "org.apache.xerces.xni.grammars", + "org.apache.xerces.xni.parser", + "org.apache.xerces.xpointer", + "org.apache.xerces.xs", + "org.apache.xerces.xs.datatypes", + "org.apache.xml.serialize", + "org.w3c.dom.html" + ], + "xml-apis:xml-apis": [ + "javax.xml", + "javax.xml.datatype", + "javax.xml.namespace", + "javax.xml.parsers", + "javax.xml.stream", + "javax.xml.stream.events", + "javax.xml.stream.util", + "javax.xml.transform", + "javax.xml.transform.dom", + "javax.xml.transform.sax", + "javax.xml.transform.stax", + "javax.xml.transform.stream", + "javax.xml.validation", + "javax.xml.xpath", + "org.apache.xmlcommons", + "org.w3c.dom", + "org.w3c.dom.bootstrap", + "org.w3c.dom.css", + "org.w3c.dom.events", + "org.w3c.dom.html", + "org.w3c.dom.ls", + "org.w3c.dom.ranges", + "org.w3c.dom.stylesheets", + "org.w3c.dom.traversal", + "org.w3c.dom.views", + "org.w3c.dom.xpath", + "org.xml.sax", + "org.xml.sax.ext", + "org.xml.sax.helpers" ] }, "repositories": { @@ -4497,9 +6455,30 @@ "androidx.work:work-testing:aar", "com.almworks.sqlite4java:sqlite4java", "com.android.databinding:baseLibrary", + "com.android.tools.analytics-library:protos", + "com.android.tools.analytics-library:shared", + "com.android.tools.apkparser:apkanalyzer", + "com.android.tools.apkparser:binary-resources", "com.android.tools.build.jetifier:jetifier-core", "com.android.tools.build:aapt2-proto", + "com.android.tools.build:builder-model", + "com.android.tools.build:builder-test-api", + "com.android.tools.build:manifest-merger", + "com.android.tools.ddms:ddmlib", + "com.android.tools.external.com-intellij:intellij-core", + "com.android.tools.external.com-intellij:kotlin-compiler", + "com.android.tools.external.org-jetbrains:uast", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools.lint:lint-api", + "com.android.tools.lint:lint-checks", + "com.android.tools.lint:lint-model", "com.android.tools:annotations", + "com.android.tools:common", + "com.android.tools:dvlib", + "com.android.tools:repository", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.beust:jcommander", "com.crashlytics.sdk.android:answers:aar", "com.crashlytics.sdk.android:beta:aar", "com.crashlytics.sdk.android:crashlytics-core:aar", @@ -4570,6 +6549,7 @@ "com.google.guava:guava", "com.google.guava:listenablefuture", "com.google.j2objc:j2objc-annotations", + "com.google.jimfs:jimfs", "com.google.protobuf:protobuf-java", "com.google.protobuf:protobuf-java-util", "com.google.protobuf:protobuf-javalite", @@ -4590,8 +6570,12 @@ "com.squareup:javapoet", "com.squareup:javawriter", "com.squareup:kotlinpoet", + "com.sun.activation:javax.activation", + "com.sun.istack:istack-commons-runtime", + "com.sun.xml.fastinfoset:FastInfoset", "commons-codec:commons-codec", "commons-io:commons-io", + "commons-logging:commons-logging", "io.fabric.sdk.android:fabric:aar", "io.grpc:grpc-android:aar", "io.grpc:grpc-api", @@ -4602,21 +6586,32 @@ "io.grpc:grpc-stub", "io.perfmark:perfmark-api", "io.xlate:yaml-json", + "jakarta.activation:jakarta.activation-api", "jakarta.json:jakarta.json-api", + "jakarta.xml.bind:jakarta.xml.bind-api", "javax.annotation:javax.annotation-api", "javax.inject:javax.inject", "junit:junit", "net.bytebuddy:byte-buddy", "net.bytebuddy:byte-buddy-agent", + "net.java.dev.jna:jna", + "net.java.dev.jna:jna-platform", "net.ltgt.gradle.incap:incap", "net.sf.kxml:kxml2", "nl.dionsegijn:konfetti:aar", "org.antlr:antlr4", + "org.apache.commons:commons-compress", + "org.apache.httpcomponents:httpclient", + "org.apache.httpcomponents:httpcore", + "org.apache.httpcomponents:httpmime", + "org.bouncycastle:bcpkix-jdk15on", "org.bouncycastle:bcprov-jdk15on", "org.checkerframework:checker-compat-qual", "org.checkerframework:checker-qual", "org.codehaus.mojo:animal-sniffer-annotations", "org.eclipse.parsson:parsson", + "org.glassfish.jaxb:jaxb-runtime", + "org.glassfish.jaxb:txw2", "org.hamcrest:hamcrest-core", "org.hamcrest:hamcrest-integration", "org.hamcrest:hamcrest-library", @@ -4641,6 +6636,7 @@ "org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", "org.jetbrains:annotations", + "org.jvnet.staxex:stax-ex", "org.mockito.kotlin:mockito-kotlin", "org.mockito:mockito-core", "org.objenesis:objenesis", @@ -4660,7 +6656,12 @@ "org.robolectric:shadows-framework", "org.robolectric:utils", "org.robolectric:utils-reflector", - "org.snakeyaml:snakeyaml-engine" + "org.smali:baksmali", + "org.smali:dexlib2", + "org.smali:util", + "org.snakeyaml:snakeyaml-engine", + "xerces:xercesImpl", + "xml-apis:xml-apis" ], "https://repo1.maven.org/maven2/": [ "androidx.activity:activity:aar", @@ -4746,9 +6747,30 @@ "androidx.work:work-testing:aar", "com.almworks.sqlite4java:sqlite4java", "com.android.databinding:baseLibrary", + "com.android.tools.analytics-library:protos", + "com.android.tools.analytics-library:shared", + "com.android.tools.apkparser:apkanalyzer", + "com.android.tools.apkparser:binary-resources", "com.android.tools.build.jetifier:jetifier-core", "com.android.tools.build:aapt2-proto", + "com.android.tools.build:builder-model", + "com.android.tools.build:builder-test-api", + "com.android.tools.build:manifest-merger", + "com.android.tools.ddms:ddmlib", + "com.android.tools.external.com-intellij:intellij-core", + "com.android.tools.external.com-intellij:kotlin-compiler", + "com.android.tools.external.org-jetbrains:uast", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools.lint:lint-api", + "com.android.tools.lint:lint-checks", + "com.android.tools.lint:lint-model", "com.android.tools:annotations", + "com.android.tools:common", + "com.android.tools:dvlib", + "com.android.tools:repository", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.beust:jcommander", "com.crashlytics.sdk.android:answers:aar", "com.crashlytics.sdk.android:beta:aar", "com.crashlytics.sdk.android:crashlytics-core:aar", @@ -4819,6 +6841,7 @@ "com.google.guava:guava", "com.google.guava:listenablefuture", "com.google.j2objc:j2objc-annotations", + "com.google.jimfs:jimfs", "com.google.protobuf:protobuf-java", "com.google.protobuf:protobuf-java-util", "com.google.protobuf:protobuf-javalite", @@ -4839,8 +6862,12 @@ "com.squareup:javapoet", "com.squareup:javawriter", "com.squareup:kotlinpoet", + "com.sun.activation:javax.activation", + "com.sun.istack:istack-commons-runtime", + "com.sun.xml.fastinfoset:FastInfoset", "commons-codec:commons-codec", "commons-io:commons-io", + "commons-logging:commons-logging", "io.fabric.sdk.android:fabric:aar", "io.grpc:grpc-android:aar", "io.grpc:grpc-api", @@ -4851,21 +6878,32 @@ "io.grpc:grpc-stub", "io.perfmark:perfmark-api", "io.xlate:yaml-json", + "jakarta.activation:jakarta.activation-api", "jakarta.json:jakarta.json-api", + "jakarta.xml.bind:jakarta.xml.bind-api", "javax.annotation:javax.annotation-api", "javax.inject:javax.inject", "junit:junit", "net.bytebuddy:byte-buddy", "net.bytebuddy:byte-buddy-agent", + "net.java.dev.jna:jna", + "net.java.dev.jna:jna-platform", "net.ltgt.gradle.incap:incap", "net.sf.kxml:kxml2", "nl.dionsegijn:konfetti:aar", "org.antlr:antlr4", + "org.apache.commons:commons-compress", + "org.apache.httpcomponents:httpclient", + "org.apache.httpcomponents:httpcore", + "org.apache.httpcomponents:httpmime", + "org.bouncycastle:bcpkix-jdk15on", "org.bouncycastle:bcprov-jdk15on", "org.checkerframework:checker-compat-qual", "org.checkerframework:checker-qual", "org.codehaus.mojo:animal-sniffer-annotations", "org.eclipse.parsson:parsson", + "org.glassfish.jaxb:jaxb-runtime", + "org.glassfish.jaxb:txw2", "org.hamcrest:hamcrest-core", "org.hamcrest:hamcrest-integration", "org.hamcrest:hamcrest-library", @@ -4890,6 +6928,7 @@ "org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", "org.jetbrains:annotations", + "org.jvnet.staxex:stax-ex", "org.mockito.kotlin:mockito-kotlin", "org.mockito:mockito-core", "org.objenesis:objenesis", @@ -4909,7 +6948,12 @@ "org.robolectric:shadows-framework", "org.robolectric:utils", "org.robolectric:utils-reflector", - "org.snakeyaml:snakeyaml-engine" + "org.smali:baksmali", + "org.smali:dexlib2", + "org.smali:util", + "org.snakeyaml:snakeyaml-engine", + "xerces:xercesImpl", + "xml-apis:xml-apis" ], "https://oss.sonatype.org/content/repositories/snapshots/": [ "androidx.activity:activity:aar", @@ -4995,9 +7039,30 @@ "androidx.work:work-testing:aar", "com.almworks.sqlite4java:sqlite4java", "com.android.databinding:baseLibrary", + "com.android.tools.analytics-library:protos", + "com.android.tools.analytics-library:shared", + "com.android.tools.apkparser:apkanalyzer", + "com.android.tools.apkparser:binary-resources", "com.android.tools.build.jetifier:jetifier-core", "com.android.tools.build:aapt2-proto", + "com.android.tools.build:builder-model", + "com.android.tools.build:builder-test-api", + "com.android.tools.build:manifest-merger", + "com.android.tools.ddms:ddmlib", + "com.android.tools.external.com-intellij:intellij-core", + "com.android.tools.external.com-intellij:kotlin-compiler", + "com.android.tools.external.org-jetbrains:uast", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools.lint:lint-api", + "com.android.tools.lint:lint-checks", + "com.android.tools.lint:lint-model", "com.android.tools:annotations", + "com.android.tools:common", + "com.android.tools:dvlib", + "com.android.tools:repository", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.beust:jcommander", "com.crashlytics.sdk.android:answers:aar", "com.crashlytics.sdk.android:beta:aar", "com.crashlytics.sdk.android:crashlytics-core:aar", @@ -5068,6 +7133,7 @@ "com.google.guava:guava", "com.google.guava:listenablefuture", "com.google.j2objc:j2objc-annotations", + "com.google.jimfs:jimfs", "com.google.protobuf:protobuf-java", "com.google.protobuf:protobuf-java-util", "com.google.protobuf:protobuf-javalite", @@ -5088,8 +7154,12 @@ "com.squareup:javapoet", "com.squareup:javawriter", "com.squareup:kotlinpoet", + "com.sun.activation:javax.activation", + "com.sun.istack:istack-commons-runtime", + "com.sun.xml.fastinfoset:FastInfoset", "commons-codec:commons-codec", "commons-io:commons-io", + "commons-logging:commons-logging", "io.fabric.sdk.android:fabric:aar", "io.grpc:grpc-android:aar", "io.grpc:grpc-api", @@ -5100,21 +7170,32 @@ "io.grpc:grpc-stub", "io.perfmark:perfmark-api", "io.xlate:yaml-json", + "jakarta.activation:jakarta.activation-api", "jakarta.json:jakarta.json-api", + "jakarta.xml.bind:jakarta.xml.bind-api", "javax.annotation:javax.annotation-api", "javax.inject:javax.inject", "junit:junit", "net.bytebuddy:byte-buddy", "net.bytebuddy:byte-buddy-agent", + "net.java.dev.jna:jna", + "net.java.dev.jna:jna-platform", "net.ltgt.gradle.incap:incap", "net.sf.kxml:kxml2", "nl.dionsegijn:konfetti:aar", "org.antlr:antlr4", + "org.apache.commons:commons-compress", + "org.apache.httpcomponents:httpclient", + "org.apache.httpcomponents:httpcore", + "org.apache.httpcomponents:httpmime", + "org.bouncycastle:bcpkix-jdk15on", "org.bouncycastle:bcprov-jdk15on", "org.checkerframework:checker-compat-qual", "org.checkerframework:checker-qual", "org.codehaus.mojo:animal-sniffer-annotations", "org.eclipse.parsson:parsson", + "org.glassfish.jaxb:jaxb-runtime", + "org.glassfish.jaxb:txw2", "org.hamcrest:hamcrest-core", "org.hamcrest:hamcrest-integration", "org.hamcrest:hamcrest-library", @@ -5139,6 +7220,7 @@ "org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", "org.jetbrains:annotations", + "org.jvnet.staxex:stax-ex", "org.mockito.kotlin:mockito-kotlin", "org.mockito:mockito-core", "org.objenesis:objenesis", @@ -5158,7 +7240,12 @@ "org.robolectric:shadows-framework", "org.robolectric:utils", "org.robolectric:utils-reflector", - "org.snakeyaml:snakeyaml-engine" + "org.smali:baksmali", + "org.smali:dexlib2", + "org.smali:util", + "org.snakeyaml:snakeyaml-engine", + "xerces:xercesImpl", + "xml-apis:xml-apis" ], "https://maven.fabric.io/public/": [ "androidx.activity:activity:aar", @@ -5244,9 +7331,30 @@ "androidx.work:work-testing:aar", "com.almworks.sqlite4java:sqlite4java", "com.android.databinding:baseLibrary", + "com.android.tools.analytics-library:protos", + "com.android.tools.analytics-library:shared", + "com.android.tools.apkparser:apkanalyzer", + "com.android.tools.apkparser:binary-resources", "com.android.tools.build.jetifier:jetifier-core", "com.android.tools.build:aapt2-proto", + "com.android.tools.build:builder-model", + "com.android.tools.build:builder-test-api", + "com.android.tools.build:manifest-merger", + "com.android.tools.ddms:ddmlib", + "com.android.tools.external.com-intellij:intellij-core", + "com.android.tools.external.com-intellij:kotlin-compiler", + "com.android.tools.external.org-jetbrains:uast", + "com.android.tools.layoutlib:layoutlib-api", + "com.android.tools.lint:lint-api", + "com.android.tools.lint:lint-checks", + "com.android.tools.lint:lint-model", "com.android.tools:annotations", + "com.android.tools:common", + "com.android.tools:dvlib", + "com.android.tools:repository", + "com.android.tools:sdk-common", + "com.android.tools:sdklib", + "com.beust:jcommander", "com.crashlytics.sdk.android:answers:aar", "com.crashlytics.sdk.android:beta:aar", "com.crashlytics.sdk.android:crashlytics-core:aar", @@ -5317,6 +7425,7 @@ "com.google.guava:guava", "com.google.guava:listenablefuture", "com.google.j2objc:j2objc-annotations", + "com.google.jimfs:jimfs", "com.google.protobuf:protobuf-java", "com.google.protobuf:protobuf-java-util", "com.google.protobuf:protobuf-javalite", @@ -5337,8 +7446,12 @@ "com.squareup:javapoet", "com.squareup:javawriter", "com.squareup:kotlinpoet", + "com.sun.activation:javax.activation", + "com.sun.istack:istack-commons-runtime", + "com.sun.xml.fastinfoset:FastInfoset", "commons-codec:commons-codec", "commons-io:commons-io", + "commons-logging:commons-logging", "io.fabric.sdk.android:fabric:aar", "io.grpc:grpc-android:aar", "io.grpc:grpc-api", @@ -5349,21 +7462,32 @@ "io.grpc:grpc-stub", "io.perfmark:perfmark-api", "io.xlate:yaml-json", + "jakarta.activation:jakarta.activation-api", "jakarta.json:jakarta.json-api", + "jakarta.xml.bind:jakarta.xml.bind-api", "javax.annotation:javax.annotation-api", "javax.inject:javax.inject", "junit:junit", "net.bytebuddy:byte-buddy", "net.bytebuddy:byte-buddy-agent", + "net.java.dev.jna:jna", + "net.java.dev.jna:jna-platform", "net.ltgt.gradle.incap:incap", "net.sf.kxml:kxml2", "nl.dionsegijn:konfetti:aar", "org.antlr:antlr4", + "org.apache.commons:commons-compress", + "org.apache.httpcomponents:httpclient", + "org.apache.httpcomponents:httpcore", + "org.apache.httpcomponents:httpmime", + "org.bouncycastle:bcpkix-jdk15on", "org.bouncycastle:bcprov-jdk15on", "org.checkerframework:checker-compat-qual", "org.checkerframework:checker-qual", "org.codehaus.mojo:animal-sniffer-annotations", "org.eclipse.parsson:parsson", + "org.glassfish.jaxb:jaxb-runtime", + "org.glassfish.jaxb:txw2", "org.hamcrest:hamcrest-core", "org.hamcrest:hamcrest-integration", "org.hamcrest:hamcrest-library", @@ -5388,6 +7512,7 @@ "org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", "org.jetbrains:annotations", + "org.jvnet.staxex:stax-ex", "org.mockito.kotlin:mockito-kotlin", "org.mockito:mockito-core", "org.objenesis:objenesis", @@ -5407,7 +7532,12 @@ "org.robolectric:shadows-framework", "org.robolectric:utils", "org.robolectric:utils-reflector", - "org.snakeyaml:snakeyaml-engine" + "org.smali:baksmali", + "org.smali:dexlib2", + "org.smali:util", + "org.snakeyaml:snakeyaml-engine", + "xerces:xercesImpl", + "xml-apis:xml-apis" ] }, "version": "2" diff --git a/third_party/versions.bzl b/third_party/versions.bzl index c1dec75774d..a90a2d476c6 100644 --- a/third_party/versions.bzl +++ b/third_party/versions.bzl @@ -99,6 +99,7 @@ MAVEN_TEST_DEPENDENCY_VERSIONS = { "androidx.test:core": "1.0.0", "androidx.test:runner": "1.2.0", "androidx.work:work-testing": "2.4.0", + "com.android.tools.apkparser:apkanalyzer": "30.0.4", "com.github.bumptech.glide:mocks": "4.11.0", "com.google.protobuf:protobuf-java": "3.17.3", "com.google.protobuf:protobuf-java-util": "3.17.3", @@ -135,6 +136,10 @@ HTTP_DEPENDENCY_VERSIONS = { "sha": "9425a423a4cb9d9db0356300722d9bd8e634cf539f29d97bb84f457cccd16eb8", "version": "31.0.1", }, + "guava_jre": { + "sha": "d5be94d65e87bd219fb3193ad1517baa55a3b88fc91d21cf735826ab5af087b9", + "version": "31.0.1", + }, "kotlinx-coroutines-core-jvm": { "sha": "acc8c74b1fb88121c51221bfa7b6f5e920201bc20183ebf74165dcf5d45a8003", "version": "1.6.0",